# 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:** 10\
**Page:** 2

<div class="post-metadata">

**Author:** ![Transistor](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/transistor/32/3380_2.png) [@Transistor](https://forum.inductiveautomation.com/u/Transistor)\
**Post date:** [June 23, 2025, 11:41pm UTC](https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080/22 "2025-06-23T23:41:36Z")

</div>

## Storing IPv4 addresses as integers (rather than strings)

String IP addresses displayed in tables don't sort naturally. e.g., x.x.x.1, x.x.x.10, x.x.x.11, x.x.x.2.  
Storing the IP addresses in the database as a 32-bit integer (which is what they are supposed to represent), and returning those to a table column but rendering them with a view to convert to four-decimal octet format solves the sorting problem.

### From IP address to integer

With the built-in expression language the job can be done in two stages (to simplify the expression):

> **Initial expression**
>
> `split({path.to.IP.as.string}, '\\.')`

> **Expression transform**
>
> ```python
> // Doing this in two steps allows the use of {value} rather than multiple splits.
> ({value}[0, 0] << 24)
> + ({value}[1, 0] << 16)
> + ({value}[2, 0] << 8)
> + ({value}[3, 0] << 0)
> 
> ```

With the Integration Toolkit it can be done in one go.

> **Integration Toolkit solution**
>
> ```python
> transform(
> split({path.to.IP.as.string}, '\\.'),
> (value()[0, 0] << 24) +
> (value()[1, 0] << 16) +
> (value()[2, 0] << 8) +
> (value()[3, 0] << 0)
> )
> 
> ```

These expressions assume that the incoming IP ≥ 1.x.x.x.

### From integer to IP address

With the built-in expression language the job can be done in two stages (to simplify the expression):

> **Initial expression**
>
> `toInt({../lblIp2IntExpression.props.text})`

> **Expression transform**
>
> ```python
> // Doing this in two steps allows the use of {value} rather than multiple splits.
> stringFormat(
> "%d.%d.%d.%d",
> ({value} >> 24) & 0xff,
> ({value} >> 16) & 0xff,
> ({value} >> 8) & 0xff,
> {value} & 0xff
> )
> 
> ```

With the Integration Toolkit it can be done in one go.

> **Integration Toolkit solution**
>
> ```python
> // This was my original attempt.
> // See Phil's improved solution below.
> transform(
> right('0' + toHex({path.to.IP.as.integer}), 8),
> fromHex(subString(value(), 0, 2)) + '.' +
> fromHex(subString(value(), 2, 4)) + '.' +
> fromHex(subString(value(), 4, 6)) + '.' +
> fromHex(subString(value(), 6, 8))
> )
> 
> ```

These expressions assume that the incoming integer represents an IP ≥ 1.x.x.x.

Edit by Phil: I would do the conversion to display without using hex strings.

> **Better Integration Toolkit Solution**
>
> ```python
> transform(
> toInt({path.to.IP.as.integer}),
> stringFormat(
> "%d.%d.%d.%d",
> (value() >> 24) & 0xff,
> (value() >> 16) & 0xff,
> (value() >> 8) & 0xff,
> value() & 0xff
> )
> )
> 
> ```

Does not require the leading octet to be ≥1.

IPv6 is not addressed by these functions.

---

<div class="post-metadata">

**Author:** ![JantjeBeton](https://avatars.discourse-cdn.com/v4/letter/j/ea666f/32.png) [@JantjeBeton](https://forum.inductiveautomation.com/u/JantjeBeton)\
**Post date:** [October 8, 2025, 12:47pm UTC](https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080/23 "2025-10-08T12:47:18Z")

</div>

## Iterating through an array-tag to determine if value is present

Casus: A camera is selected on a video-wall if it’s ID (UDT-parameter) is present in a tag of the type Integer array (external system). So I want to use an expression-tag to determine if the camera is selected (boolean).

Approach: Iterate through the array with forEach (or where?) and compare each value to the integer ID-value. If found -\> true, else false

> **Initial expression**
>
> forEach returns a list if expression is met?  
> Then check if this list has one or more entries.
> 
> ```python
> if(
> len(
> forEach( 
> {[~]MSBS/SLKRS1_I1/CCTV/Pnl/STS/CameraNr.value}, 
> it() = {ArrayID}
> )
> ) > 0,
> true,
> false
> )
> 
> ```
> 
> Status: Error in the tag (Error Config)? What do I do wrong?  
> Tips:  
> `forEach()` always returns a list. But per my previous reply, use `where()` instead of `forEach()`. Do not wrap an `if()` around the whole thing, as a comparison with `len()` yields a boolean directly.

> **Improved expression**
>
> Note: I've moved the source (array) tag to be in the same folder as the expression-tag in order to supply an export-file (attached) if anybody is interested in reproducing/solving the issue.
> 
> ```python
> len(
> where( 
> {[.]CameraNr.value}, 
> it() = {ArrayID}
> )
> ) > 0
> 
> ```
> 
> Previous improvements applied, still an Error\_Configuration (Error writing to Geselecteerd.value: Bad\_Unsupported)

[ExpressionTagAndSource.json](https://forum.inductiveautomation.com/uploads/short-url/70ovzl90r6io7bnKMuGdpiKggA4.json) (784 Bytes)

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [October 13, 2025, 12:23pm UTC](https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080/24 "2025-10-13T12:23:39Z")

</div>

# Creating a list of Months in format `YYYY-MM` between a start and end date

Note: `forEach(10, it())` is equivalent to `range(10)` in Python.

Expression Structure Binding:

```python
start: 2025-06-01 0:00:00
end: 2025-10-01 00:00:00

```

```python
forEach(
	forEach(monthsBetween({value}['start'], {value}['end'])+1, it()),
	dateFormat(addMonths({value}['start'], it()), 'yyyy-MM')
)

```

Returns:

```python
[
  "2025-06",
  "2025-07",
  "2025-08",
  "2025-09",
   "2025-10"
]

```

One of the `forEach()` loops is unneeded. `forEach()` returns a list, so your outer loop is actually `forEach(asList(0,1,2,3,4),it())`

```python
forEach(
    monthsBetween({value}['start'],{value['end']) + 1,
    dateFormat(addMonths({value}['start'],it()),'yyyy-MM')
)

```

Will yeild the same result.

> **Features employed**
>
> - [Iterables](https://www.automation-pros.com/toolkit/doc/iteration.html#iterables) General behavior of all iterators.
> - [`forEach()`](https://www.automation-pros.com/toolkit/doc/iteration.html#foreach) Loops through the dataset, calling the nested expression with one row at a time.
> - [`it()`](https://www.automation-pros.com/toolkit/doc/iteration.html#it) Delivers the one row in dataset format (same column names and types as the source).
> - [`monthsBetween()`](https://docs.inductiveautomation.com/docs/8.1/appendix/expression-functions/date-and-time/Between) Calculates the number of whole months between two dates.
> - [`addMonths()`](https://docs.inductiveautomation.com/docs/8.1/appendix/expression-functions/date-and-time/add) Add or subtract an amount of months to a given date and time.
> - [`dateFormat()`](https://docs.inductiveautomation.com/docs/8.1/appendix/expression-functions/date-and-time/dateFormat) Returns the given date as a string, formatted according to a pattern.

_Pssst!_ Use `yyyy-MM` to avoid year-end confusion over week-years.

---

<div class="post-metadata">

**Author:** ![lrose](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/lrose/32/62724_2.png) [@lrose](https://forum.inductiveautomation.com/u/lrose)\
**Post date:** [November 13, 2025, 1:51pm UTC](https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080/25 "2025-11-13T13:51:43Z")

</div>

# Adding Columns to a Dataset where the Column Data is derived from expressions

The [selectStar()](https://www.automation-pros.com/toolkit/doc/datasets.html#selectstar) expression can be used to add one or more columns to a data set. As implied by the name this expression is roughly equivilent to a `SELECT * FROM Table1` query in SQL. The advantage here is that the expression iterates through each row, allowing you to derive a new columns value from other data in the table.

Obviously the data expression does not need to reference the existing data. Any valid expression which returns the correct datatype can be used.

> **Example Expression Single Column**
>
> ```python
> selectStar(
> {path.to.originalDataset},
> asMap('New Column', 'I'),
> it()['Column 1'] + 10
> )
> 
> ```

> **Example Expression Multiple Columns**
>
> ```python
> selectStar(
> {path.to.originalDataset},
> asMap('New Column', 'I','New Column 2','I'),
> it()['Column 1'] + 10,
> it()['Column 1'] * 10
> )
> 
> ```

> **Features Used**
>
> - [selectStar()](https://www.automation-pros.com/toolkit/doc/iteration.html#foreach) Adds columns to a dataset with row-by-row computation of the contents of the new columns.
> - [asMap()](https://www.automation-pros.com/toolkit/doc/iteration.html#asmap) Creates a map of the list of key-value pairs.
> - [it()['columnName']](https://www.automation-pros.com/toolkit/doc/iteration.html#it) Delivers a cell value based on the column name.

Phil: I often use `selectStar()` when I need an original row index to be retained through a `where()` operation. Something like this:

```python
where(
    selectStar(
        {path.to.original.data},
        asMap('original_row_idx', 'I'),
        idx()
    ),
    ... some condition ...
)

```

---

<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:** [March 20, 2026, 1:45pm UTC](https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080/26 "2026-03-20T13:45:20Z")

</div>

## Calculate Pareto Line for a Chart

Take a dataset and calculate the running percentage for a pareto line (80/20 line).

> **Pareto Calculation**
>
> ```python
> transform(
> // get the total event count to reuse in pareto calc
> // replace `count` with your total column name, {value} = your data
> sum(forEach({value},it()['count'])),
> 
> // loop and get running total for pareto
> // Use the outer idx() to determine which inner values should be totaled for the running calc
> forEach(
> {value},
> asMap(
> 'category', it()['category'],
> 'count', it()['count'],
> 'pareto', sum(
> forEach(
> {value},
> if(idx() < idx(1)+1,it()['count'],0) 
> )
> ) / value()			
> )
> )
> )
> 
> ```

> **Features employed**
>
> - [transform()](https://www.automation-pros.com/toolkit/doc/utility.html#transform) Enables an expression to be re-usable throughout a second expression.
> - [value()](https://www.automation-pros.com/toolkit/doc/utility.html#value) Reuses the transform()'s stored, first expression in the second expression as needed.
> - [forEach()](https://www.automation-pros.com/toolkit/doc/iteration.html#foreach) Loops over an iterator.
> - [asMap()](https://www.automation-pros.com/toolkit/doc/iteration.html#asmap) Creates a key-value object (Java Map).
> - [it()['columnName']](https://www.automation-pros.com/toolkit/doc/iteration.html#it) Delivers a cell value based on the column name.

_Exercise for the reader:_ re-implement with `groupBy()` for performance...

---

<div class="post-metadata">

**Author:** ![lrose](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/lrose/32/62724_2.png) [@lrose](https://forum.inductiveautomation.com/u/lrose)\
**Post date:** [April 30, 2026, 7:35pm UTC](https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080/27 "2026-04-30T19:35:22Z")

</div>

# Aggregate Columns by Fiscal Day

Ok, I'm struggling on this one.

What I want to do is take a dataset with daily production numbers by hour and aggreate it for a month or week by day. Doing so with just the day is simple :

Raw Data takes the shape: `[datetime, hourProduction]` where hourProduction is an integer.

> **Simple Group By Day of Month**
>
> ```python
> transform(
> {Path.to.rawData},
> unionAll(
> columnsOf(value()),
> forEach(
> groupBy( value(), getDayOfMonth(it())),
> asList(
> dateFormat( it()[1][0], 'MM/d'),
> sum(it()[1],1)
> )
> )
> )
> )
> 
> ```

What I can't figure out is how to form the key expression in order to group by our fiscal day. 6a to 6a. So any data between 12am and 6am counts for the previous calendar day.

EDIT: Apparently I can't make the post a wiki any longer?

Phil: Made it a wiki for you. Not sure why you couldn't.

The simplistic solution is to subtract 6 six hours from the value fed to `getDayOfMonth()`. (Breaks the Sunday of a time change.) Something like

```python
groupBy(value(), getDayOfMonth(addHours(it()[0], -6)))

```

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [May 15, 2026, 3:42pm UTC](https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080/28 "2026-05-15T15:42:40Z")

</div>

# Joining elements of an array into a string with delimiter with slicing

To mimic this python code:

```python
viewPath = 'This/That/Other/Here'
parts = viewPath.split('/')
result = '/'.join(parts[:2])

```

Use this:

```python
groupConcat(
	where(
		foreach(split({view.custom.currentPrimaryView}, '/'), it()[0]),
		idx() < 2
	),
'/')

```

@pturmel thanks for the hint, I've never used that native function before!

You can further simplify, BTW, since `where()` and `groupConcat()` can both take datasets. Eliminating the `forEach()`.

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [May 18, 2026, 1:37am UTC](https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080/29 "2026-05-18T01:37:46Z")

</div>

# Adding styling to a table row where a cell equals a value

From [this](https://forum.inductiveautomation.com/t/perspective-table-dynamic-row-highlight-based-on-dataset-tag-value-match/115562) OP, this expression uses a dataset to populate a Table and applies styling to the row in the dataset where the column `Prod. Order No` equals the value in the tag `{[default]IGBT_ROBOT_CELL_PROD/OrderNumber}`

```python
transform(
    {[default]IGBT_ROBOT_CELL_PROD/OrderNumber},
    forEach(
        {[default]IGBT_ROBOT_CELL_PROD/SAP_RES_Dataset},
        if(
            it()['Prod. Order No'] = value(),
            asMap(
                forEach(
                    it(), // key/value pairs of the row
                    it()[0], // column key
                    asMap(
                        'value', asMap(it()[1]), // column value nested
                        'style', asMap('backgroundColor', '#ff3333')
                    )
                )
            ),
            asMap(it()) // optimized unstyled conversion of a whole row
        )
    )
)

```

Phil: Compare to the simply jsonification shown in comment #2.

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [May 26, 2026, 1:08pm UTC](https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080/30 "2026-05-26T13:08:03Z")

</div>

# Converting a `tags()` output into a dict

Say you had a list of tag paths like this:

- AreaA/P001/Sts/Status
- AreaA/P002/Sts/Status
- AreaA/P003/Sts/Status

and you want to produce a dict with the equipment name and status tag value, e.g.

```python
{
  'P001': 'Running',
  'P002': 'Stopped', :face_with_hand_over_mouth:
  'P003': 'Running'
}

```

. You can use:

```python
view.custom.tagPaths: <your tag list>
view.custom.pumpStatuses:
asMap(
   forEach(
      tags({view.custom.tagPaths}),
      transform(split(it()[0], '/'), value()[len(value())-3, 0]),
      it()[1]
   )
)   

```

PS. I'm sure someone will tell me there's a simpler way...  
Phil: I would put the `tags()` function directly as the source for the `forEach()`. Just the actual list of tag paths would be in the custom prop. I would use the `parsePath()` function to extract the tag path inside the loop, and split just that part--otherwise you cannot use the first element when necessary. If you need more than one object level, you can use grouping on partial tag paths.

Nick: oh, using `tag` in the pumpStatuses expression is actually what I _did_ do in reality, I just bungled it up here 🤭 I need to look into your parsePath as it's currently a mystery to me!

---

<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)

[Previous page](https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080.md?page=1)
