# Integration Toolkit Solutions Wiki

**URL:** <https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080>\
**Category:** 3rd Party Modules\
**Tags:** scripting, ignition81\
**Created:** [April 14, 2025, 9:02pm UTC](https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080 "2025-04-14T21:02:36Z")\
**Posts on this page:** 1\
**Showing post:** 31

<div class="post-metadata">

**Author:** ![dkhayes117](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/dkhayes117/32/68344_2.png) [@dkhayes117](https://forum.inductiveautomation.com/u/dkhayes117)\
**Post date:** [September 4, 2026, 8:01pm UTC](https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080/31 "2026-09-04T20:01:52Z")

</div>

## Editing Perspective Table Datasets with Difference Styling

This allows a user to edit a dataset, view, and accept or reject the changes in a Git diff kind of manner. I'm sure some optimizations can be added to my example.

The example view has a `rawData` custom prop that holds the original data, an `EditData` prop for the live edited data, and a `diffData` dataset prop that shows the difference between the two.

The `diffChanges` prop holds only the changes for reviewing and auditing.

> **Getting all Data Edits**
>
> ```python
> // Left join original → edited on the PK
> // Alias both sides to avoid column name clashes
> // unionAll three categories: DELETED, MODIFIED, ADDED
> unionAll(
> // Column schema for a edit list
> asMap(
> "change_type", "String",
> "primary_key", "String",
> "column", "String",
> "old_value", "String",
> "new_value", "String"
> ),
> // DELETED: in original, not in edited ------------------------------------------//
> forEach(
> where(
> leftJoin(
> alias({view.custom.rawData}, "raw_"),
> alias({view.custom.editData}, "edit_"),
> it()["raw_city"],
> it()["edit_city"]
> ),
> it()["edit_city"] = null
> ),
> asList(
> "DELETED",
> it()["raw_city"],
> null,
> null,
> null,
> it()["raw_city"]
> )
> ),
> // MODIFIED: in both, but have cell differences----------------------------------//
> flatten(
> forEach(
> // Outer loop: joined rows where at least one cell differs
> where(
> leftJoin(
> alias({view.custom.rawData}, "raw_"),
> alias({view.custom.editData}, "edit_"),
> it()["raw_city"],
> it()["edit_city"]
> ),
> it()["edit_city"] != null,
> len(
> where(
> columnsOf({view.custom.rawData}),
> toString(it(1)["raw_" + it()[0]]) != toString(it(1)["edit_" + it()[0]])
> )
> ) > 0
> ),  
> // Inner loop: one result row per changed cell in this joined row
> forEach(
> where(
> columnsOf({view.custom.rawData}),
> toString(it(1)["raw_" + it()[0]]) != toString(it(1)["edit_" + it()[0]])
> ),
> asList(
> "MODIFIED",
> it(1)["raw_city"],
> it()[0],
> toString(it(1)["raw_" + it()[0]]),
> toString(it(1)["edit_" + it()[0]])
> )
> )
> )),
> // ADDED: in edited, not in original ----------------------------------------------//
> forEach(
> where(
> leftJoin(
> alias({view.custom.editData}, "edit_"),
> alias({view.custom.rawData}, "raw_"),
> it()["edit_city"],
> it()["raw_city"]
> ),
> it()["raw_city"] = null
> ),
> asList(
> "ADDED",
> it()["edit_city"],
> null,
> null,
> it()["edit_city"]
> )
> )
> )
> 
> ```

Note that some column must act like a primary key, this could be abstracted from the expression into a separate prop.

> **Adding deleted rows to the edited data and color code**
>
> ```python
> forEach(
> // This adds deleted rows to the edited data so the data diff view can display it
> unionAll(
> // Schema comes from the edited dataset
> columnsOf({view.custom.editData}),
> // All edited rows
> {view.custom.editData},
> // Only deleted rows from original
> where(
> {view.custom.rawData},
> len(
> where(
> {view.custom.dataChanges},
> it()["primary_key"] = it(1)["city"],
> it()["change_type"] = "DELETED"
> )
> ) > 0
> )
> ),
> // convert dataset to JSON for custom coloring
> asMap(
> forEach(
> asMap(it()), 
> it()[0], 
> asMap(
> 'value', it()[1],'style', 
> asMap(
> 'backgroundColor',
> case(true,
> len(where({view.custom.dataChanges}, 
> it()['primary_key'] = it(2)['city'] && it()['change_type'] = 'ADDED')) > 0, 'lawngreen',
> len(where({view.custom.dataChanges}, 
> it()['primary_key'] = it(2)['city'] && it()['column'] = it(1)[0])) > 0, 'khaki',
> len(where({view.custom.dataChanges}, 
> it()['primary_key'] = it(2)['city'] && it()['change_type'] = 'DELETED')) > 0, 'lightcoral',
> ''
> )	
> )
> )
> )
> )
> )
> 
> ```

> **Example v.81 Perspective View**
>
> [
> 
> ```python
> {
> "type": "ia.container.flex",
> "version": 0,
> "props": {
> "direction": "column"
> },
> "meta": {
> "name": "root"
> },
> "position": {},
> "custom": {},
> "children": [
> {
> "type": "ia.container.flex",
> "version": 0,
> "props": {
> "alignItems": "center"
> },
> "meta": {
> "name": "HeaderFlex"
> },
> "position": {
> "basis": "50px"
> },
> "custom": {},
> "children": [
> {
> "type": "ia.display.label",
> "version": 0,
> "props": {
> "text": "Data Diff (Edits are made in this table)",
> "textStyle": {
> "fontWeight": "bold"
> }
> },
> "meta": {
> "name": "Label"
> },
> "position": {
> "grow": 1
> },
> "custom": {}
> },
> {
> "type": "ia.input.button",
> "version": 0,
> "props": {
> "text": "Submit Edits",
> "primary": false,
> "image": {
> "icon": {
> "path": "material/save_alt",
> "color": "limegreen"
> }
> }
> },
> "meta": {
> "name": "Button_0"
> },
> "position": {
> "shrink": 0,
> "basis": "125px"
> },
> "custom": {},
> "propConfig": {
> "props.enabled": {
> "binding": {
> "type": "expr",
> "config": {
> "expression": "len({view.custom.dataChanges}) > 0"
> }
> }
> }
> },
> "events": {
> "component": {
> "onActionPerformed": {
> "type": "script",
> "scope": "G",
> "config": {
> "script": "\tself.view.custom.rawData = self.view.custom.editData"
> }
> }
> }
> }
> },
> {
> "type": "ia.input.button",
> "version": 0,
> "props": {
> "text": "Reject Edits",
> "primary": false,
> "image": {
> "icon": {
> "path": "material/restore_from_trash",
> "color": "lightcoral"
> }
> }
> },
> "meta": {
> "name": "Button"
> },
> "position": {
> "shrink": 0,
> "basis": "120px"
> },
> "custom": {},
> "propConfig": {
> "props.enabled": {
> "binding": {
> "type": "expr",
> "config": {
> "expression": "len({view.custom.dataChanges}) > 0"
> }
> }
> }
> },
> "events": {
> "component": {
> "onActionPerformed": {
> "type": "script",
> "scope": "G",
> "config": {
> "script": "\tself.view.refreshBinding('custom.editData')"
> }
> }
> }
> }
> }
> ]
> },
> {
> "type": "ia.container.flex",
> "version": 0,
> "props": {
> "alignItems": "center"
> },
> "meta": {
> "name": "NewRowFlex"
> },
> "position": {
> "basis": "50px"
> },
> "custom": {},
> "children": [
> {
> "type": "ia.input.text-field",
> "version": 0,
> "props": {
> "placeholder": "city..."
> },
> "meta": {
> "name": "TextField_City"
> },
> "position": {
> "grow": 1,
> "basis": "25%"
> },
> "custom": {},
> "propConfig": {
> "props.text": {
> "binding": {
> "type": "property",
> "config": {
> "path": "view.custom.newRow_City",
> "bidirectional": true
> }
> }
> }
> }
> },
> {
> "type": "ia.input.text-field",
> "version": 0,
> "props": {
> "placeholder": "country..."
> },
> "meta": {
> "name": "TextField_County"
> },
> "position": {
> "grow": 1,
> "basis": "25%"
> },
> "custom": {},
> "propConfig": {
> "props.text": {
> "binding": {
> "type": "property",
> "config": {
> "path": "view.custom.newRow_Country",
> "bidirectional": true
> }
> }
> }
> }
> },
> {
> "type": "ia.input.numeric-entry-field",
> "version": 0,
> "props": {
> "placeholder": "population..."
> },
> "meta": {
> "name": "NumericEntryField_Population"
> },
> "position": {
> "grow": 1,
> "basis": "25%"
> },
> "custom": {},
> "propConfig": {
> "props.value": {
> "binding": {
> "type": "property",
> "config": {
> "path": "view.custom.newRow_Population",
> "bidirectional": true
> }
> }
> }
> }
> },
> {
> "type": "ia.input.button",
> "version": 0,
> "props": {
> "text": "Add Row",
> "primary": false,
> "image": {
> "icon": {
> "path": "material/playlist_play",
> "color": "limegreen"
> }
> }
> },
> "meta": {
> "name": "Button_AddRow"
> },
> "position": {
> "shrink": 0,
> "basis": "125px"
> },
> "custom": {},
> "propConfig": {
> "props.enabled": {
> "binding": {
> "type": "expr",
> "config": {
> "expression": "{view.custom.newRow_City} != ''\r\n\t&& {view.custom.newRow_Country} != ''\r\n\t&& {view.custom.newRow_Population} != null"
> }
> }
> }
> },
> "events": {
> "component": {
> "onActionPerformed": {
> "type": "script",
> "scope": "G",
> "config": {
> "script": "\tdataIn = self.view.custom.editData\n\tnewRow = [\n\t\tself.view.custom.newRow_City,\n\t\tself.view.custom.newRow_Country,\n\t\tself.view.custom.newRow_Population,\n\t]\n\t\n\tdataOut = system.dataset.addRow(dataIn, newRow)\n\tself.view.custom.editData = dataOut\n\t\n\tself.view.custom.newRow_City = ''\n\tself.view.custom.newRow_Country = ''\n\tself.view.custom.newRow_Population = None\n\t"
> }
> }
> }
> }
> },
> {
> "type": "ia.input.button",
> "version": 0,
> "props": {
> "text": "Delete Row",
> "primary": false,
> "image": {
> "icon": {
> "path": "material/playlist_play",
> "color": "lightcoral"
> }
> }
> },
> "meta": {
> "name": "Button"
> },
> "position": {
> "shrink": 0,
> "basis": "120px"
> },
> "custom": {},
> "propConfig": {
> "props.enabled": {
> "binding": {
> "type": "expr",
> "config": {
> "expression": "len({view.custom.selectedData}) > 0"
> }
> }
> }
> },
> "events": {
> "component": {
> "onActionPerformed": {
> "type": "script",
> "scope": "G",
> "config": {
> "script": "\tdataIn = self.view.custom.editData\n\tselectedRow = self.view.custom.selectedRowIndex\n\t\n\tdataOut = system.dataset.deleteRow(dataIn, selectedRow)\n\tself.view.custom.editData = dataOut"
> }
> }
> }
> }
> }
> ]
> },
> {
> "type": "ia.display.table",
> "version": 0,
> "props": {
> "selection": {
> "selectedColumn": "country"
> },
> "columns": [
> {
> "field": "city",
> "visible": true,
> "editable": false,
> "render": "auto",
> "justify": "auto",
> "align": "center",
> "resizable": true,
> "sortable": true,
> "sort": "none",
> "filter": {
> "enabled": false,
> "visible": "on-hover",
> "string": {
> "condition": "",
> "value": ""
> },
> "number": {
> "condition": "",
> "value": ""
> },
> "boolean": {
> "condition": ""
> },
> "date": {
> "condition": "",
> "value": ""
> }
> },
> "viewPath": "",
> "viewParams": {},
> "boolean": "checkbox",
> "number": "value",
> "progressBar": {
> "max": 100,
> "min": 0,
> "bar": {
> "color": "",
> "style": {
> "classes": ""
> }
> },
> "track": {
> "color": "",
> "style": {
> "classes": ""
> }
> },
> "value": {
> "enabled": true,
> "format": "0,0.##",
> "justify": "center",
> "style": {
> "classes": ""
> }
> }
> },
> "toggleSwitch": {
> "color": {
> "selected": "",
> "unselected": ""
> }
> },
> "nullFormat": {
> "includeNullStrings": false,
> "strict": false,
> "nullFormatValue": ""
> },
> "numberFormat": "0,0.##",
> "dateFormat": "MM/DD/YYYY",
> "width": "",
> "strictWidth": false,
> "style": {
> "classes": ""
> },
> "header": {
> "title": "",
> "justify": "left",
> "align": "center",
> "style": {
> "classes": ""
> }
> },
> "footer": {
> "title": "",
> "justify": "left",
> "align": "center",
> "style": {
> "classes": ""
> }
> }
> },
> {
> "field": "country",
> "visible": true,
> "editable": false,
> "render": "auto",
> "justify": "auto",
> "align": "center",
> "resizable": true,
> "sortable": true,
> "sort": "none",
> "filter": {
> "enabled": false,
> "visible": "on-hover",
> "string": {
> "condition": "",
> "value": ""
> },
> "number": {
> "condition": "",
> "value": ""
> },
> "boolean": {
> "condition": ""
> },
> "date": {
> "condition": "",
> "value": ""
> }
> },
> "viewPath": "",
> "viewParams": {},
> "boolean": "checkbox",
> "number": "value",
> "progressBar": {
> "max": 100,
> "min": 0,
> "bar": {
> "color": "",
> "style": {
> "classes": ""
> }
> },
> "track": {
> "color": "",
> "style": {
> "classes": ""
> }
> },
> "value": {
> "enabled": true,
> "format": "0,0.##",
> "justify": "center",
> "style": {
> "classes": ""
> }
> }
> },
> "toggleSwitch": {
> "color": {
> "selected": "",
> "unselected": ""
> }
> },
> "nullFormat": {
> "includeNullStrings": false,
> "strict": false,
> "nullFormatValue": ""
> },
> "numberFormat": "0,0.##",
> "dateFormat": "MM/DD/YYYY",
> "width": "",
> "strictWidth": false,
> "style": {
> "classes": ""
> },
> "header": {
> "title": "",
> "justify": "left",
> "align": "center",
> "style": {
> "classes": ""
> }
> },
> "footer": {
> "title": "",
> "justify": "left",
> "align": "center",
> "style": {
> "classes": ""
> }
> }
> },
> {
> "field": "population",
> "visible": true,
> "editable": true,
> "render": "auto",
> "justify": "auto",
> "align": "center",
> "resizable": true,
> "sortable": true,
> "sort": "none",
> "filter": {
> "enabled": false,
> "visible": "on-hover",
> "string": {
> "condition": "",
> "value": ""
> },
> "number": {
> "condition": "",
> "value": ""
> },
> "boolean": {
> "condition": ""
> },
> "date": {
> "condition": "",
> "value": ""
> }
> },
> "viewPath": "",
> "viewParams": {},
> "boolean": "checkbox",
> "number": "value",
> "progressBar": {
> "max": 100,
> "min": 0,
> "bar": {
> "color": "",
> "style": {
> "classes": ""
> }
> },
> "track": {
> "color": "",
> "style": {
> "classes": ""
> }
> },
> "value": {
> "enabled": true,
> "format": "0,0.##",
> "justify": "center",
> "style": {
> "classes": ""
> }
> }
> },
> "toggleSwitch": {
> "color": {
> "selected": "",
> "unselected": ""
> }
> },
> "nullFormat": {
> "includeNullStrings": false,
> "strict": false,
> "nullFormatValue": ""
> },
> "numberFormat": "0,0.##",
> "dateFormat": "MM/DD/YYYY",
> "width": "",
> "strictWidth": false,
> "style": {
> "classes": ""
> },
> "header": {
> "title": "",
> "justify": "left",
> "align": "center",
> "style": {
> "classes": ""
> }
> },
> "footer": {
> "title": "",
> "justify": "left",
> "align": "center",
> "style": {
> "classes": ""
> }
> }
> }
> ],
> "rows": {
> "striped": {
> "color": {
> "even": "#FFFFFF",
> "odd": "#D5D5D5"
> }
> }
> },
> "pager": {
> "bottom": false
> }
> },
> "meta": {
> "name": "DiffTable"
> },
> "position": {
> "basis": "400px"
> },
> "custom": {},
> "propConfig": {
> "props.data": {
> "binding": {
> "type": "property",
> "config": {
> "path": "view.custom.diffData"
> }
> }
> },
> "props.selection.data": {
> "binding": {
> "type": "property",
> "config": {
> "path": "view.custom.selectedData",
> "bidirectional": true
> }
> }
> },
> "props.selection.selectedRow": {
> "binding": {
> "type": "property",
> "config": {
> "path": "view.custom.selectedRowIndex",
> "bidirectional": true
> }
> }
> }
> },
> "events": {
> "component": {
> "onEditCellCommit": {
> "type": "script",
> "scope": "G",
> "config": {
> "script": "\tdataIn = self.view.custom.editData\n\tr = event.row\n\tc = event.column\n\t\n\tdataOut = system.dataset.setValue(dataIn, r, c, event.value)\n\tself.view.custom.editData = dataOut"
> }
> }
> }
> }
> },
> {
> "type": "ia.display.label",
> "version": 0,
> "props": {
> "text": "Edited Data",
> "textStyle": {
> "fontWeight": "bold"
> }
> },
> "meta": {
> "name": "Label_0"
> },
> "position": {
> "basis": "50px"
> },
> "custom": {}
> },
> {
> "type": "ia.display.table",
> "version": 0,
> "props": {
> "selection": {
> "selectedRow": ""
> },
> "rows": {
> "striped": {
> "color": {
> "even": "#FFFFFF",
> "odd": "#D5D5D5"
> }
> }
> },
> "pager": {
> "bottom": false
> }
> },
> "meta": {
> "name": "EditTable"
> },
> "position": {
> "basis": "400px"
> },
> "custom": {},
> "propConfig": {
> "props.data": {
> "binding": {
> "config": {
> "path": "view.custom.editData"
> },
> "type": "property"
> }
> }
> }
> },
> {
> "type": "ia.display.label",
> "version": 0,
> "props": {
> "text": "RawData",
> "textStyle": {
> "fontWeight": "bold"
> }
> },
> "meta": {
> "name": "Label"
> },
> "position": {
> "basis": "50px"
> },
> "custom": {}
> },
> {
> "type": "ia.display.table",
> "version": 0,
> "props": {
> "rows": {
> "striped": {
> "color": {
> "even": "#FFFFFF",
> "odd": "#D5D5D5"
> }
> }
> },
> "pager": {
> "bottom": false
> }
> },
> "meta": {
> "name": "RawTable"
> },
> "position": {
> "basis": "400px"
> },
> "custom": {},
> "propConfig": {
> "props.data": {
> "binding": {
> "config": {
> "path": "view.custom.rawData"
> },
> "type": "property"
> }
> }
> }
> }
> ]
> }
> ]
> 
> ```

![DatasetDiff](https://us1.discourse-cdn.com/inductiveautomation/original/3X/c/a/ca7bc25492f216feca943c67936c7777fdc60aaf.gif)

---

_[View the full topic](https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080)._
