SINCE VERSION 5.0 FEATURE TAG: F-T37Y
What is the List Framework?
The List Framework in PIPEFORCE renders a given JSON Array as HTML table list in the web portal. This table can then ordered, searched, and filtered.
The JSON Array is referred to as the list input model. It is returned by a pipeline that consolidates data from various locations and provides this data as a JSON Array. The portal finally renders this array to a HTML table:
This is how such a table will look like in the portal:
In order to implement a list in PIPEFORCE, you have to create two JSON documents:
List Schema
Defines the structure of row items and columns of the table. For example the header, the data type and the conversion rules if required. This document is typically located at$APP_HOME/schema/<type>
.List Config
Defines from which source to load the JSON Array for the list from. This document is typically located at$APP_HOME/list/<type>
.
After these two JSON documents have been created, the list is shown in the app view as list tile. When clicking this tile, the list will be loaded.
Quick Start
In case you are in a hurry or you want to create your first list without going into details, follow these steps in this quick start:
Step 1: Create a List Schema
The first step is to create the List Schema. This is a JSON file which specifies the columns of the list in the JSON Schema format.
Login as a developer and go to LOW CODE -> Properties
.
Select an existing app or create a new one first using the App
wizard.
For example with app name io.pipeforce.myapp
.
Then, click the List Schema
wizard and set as name person
. Click CREATE
:
After this step, a List Schema will be created at path global/app/io.pipeforce.myapp/schema/person
, pre-filled with some demo fields similar to this:
{ "type": "object", "properties": { "firstName": { "type": "string", "title": "First Name", "description": "The first name of the person." }, "lastName": { "type": "string", "title": "Last Name", "description": "The last name of the person." }, "age": { "type": "number", "title": "Age", "description": "The age of the person." }, "gender": { "type": "string", "title": "Gender", "description": "The gender of the person.", "enum": [ "male", "female", "neutral" ] }, "birthDate": { "type": "string", "format": "date", "title": "Date of birth", "description": "The date of birth of the person." } } }
A List Schema is 100% a JSON Schema. Here you define the structure of a row of your input model to the list. Each field on first level of the schema will represent a column configuration of your list. You can change the demo data in it later and adjust it to fit your needs.
Step 2: Create a List Config
The next step is to create the List Config. This is also a JSON file which configures for example where to load the data for the list from, what the name of the list is and more.
Go to LOW CODE -> Properties
.
Select your app, for example io.pipeforce.myapp
.
Click on the List Config
wizard. Set as name also person
and click CREATE
:
This will create the List Config as JSON property at path global/app/io.pipeforce.myapp/list/person
which will look similar to this example:
{ "title": "person", "description": "", "schema": "$uri:property:global/app/io.pipeforce.myapp/schema/person", "input": "$uri:command:property.value.list?pattern=global/app/io.pipeforce.myapp/data/person/*" }
As you can see in this configuration, there are four main attributes:
title
: The title of the list to be displayed in the app.description
: The optional description of the list.schema
: A client side PIPEFORCE URI which points to the location where the List Schema can be loaded from. As you can see, in this example this is the path to the List Schema we have created in step 1.input
: A client side PIPEFORCE URI which points to the location where the data for the list can be loaded from. In this example it points to a single commandproperty.value.list
. But it can also point for example to a persisted pipeline which loads, prepares and returns the input model for the list.
Step 3: Create Input Model (data)
At this point, you have everything you need to display your list. However, you still need to provide the data for it, and this is what we will do in this step.
If you take a closer look again into the List Config, you will notice the attribute input
which points to the location where the input for the list can be loaded from. In this example, the command property.value.list
is used which will load all property values from the property store at a given path given by the pattern
attribute. In this example, this pattern
is set to global/app/io.pipeforce.myapp/data/person/*
. This means, the list expects in this property path JSON properties to be returned.
Go to LOW CODE -> Playground
.
Copy & paste and execute this pipeline:
pipeline: - property.value.list: pattern: global/app/io.pipeforce.myapp/data/person/*
As a result []
will be returned:
The reason for this result is, because currently there is no data yet at location global/app/io.pipeforce.myapp/data/person/*
. To create demo data there, copy & paste this pipeline into the playground and execute it:
pipeline: - property.schema.put: path: global/app/io.pipeforce.myapp/data/person/1 type: application/json value: { "firstName": "Nancy", "lastName": "Mergs", "age": 30, "gender": "female", "birthDate": "01/02/1990" } - property.schema.put: path: global/app/io.pipeforce.myapp/data/person/2 type: application/json value: { "firstName": "Sarah", "lastName": "Smith", "age": 33, "gender": "female", "birthDate": "05/07/1987" } - property.schema.put: path: global/app/io.pipeforce.myapp/data/person/3 type: application/json value: { "firstName": "Max", "lastName": "Meyrs", "age": 50, "gender": "male", "birthDate": "02/09/1973" }
This will create three simple demo JSON documents:
global/app/io.pipeforce.myapp/data/person/1
global/app/io.pipeforce.myapp/data/person/2
global/app/io.pipeforce.myapp/data/person/3
Execute again this pipeline in the Playground:
pipeline: - property.value.list: pattern: global/app/io.pipeforce.myapp/data/person/*
As a result, you will see a JSON Array this time with exactly the JSON properties as entries, we have created before:
This is also exactly the input model for our list.
Now we have everything for our list, including data.
Go to APPS -> Installed Apps
.
Click your app, for example io.pipeforce.myapp
.
Click person
to show the list which looks similar to this:
Done. Your list is ready to be used.
For more details how to customize the list to fit even better your needs, read the details below.
List Input Model
The input model to the list is always a root JSON Array. The entries of this root JSON Array are the rows of the list whereas these rows can be one of:
JSON Array row items:
[ ["row1-col1", "row1-col2", "row1-col3"], ["row2-col1", "row2-col2", "row2-col3"] ]
JSON Object row items:
[ {"col1": "row1-col1", "col2": "row1-col2", "col3": "row1-col3"}, {"col1": "row2-col1", "col2": "row2-col2", "col3": "row2-col3"} ]
All row items of a single List Input Model must have the same data structure.
Primitive and nested objects are allowed as values.
JSON Array row items
The list input model can be of this format where each row is itself is a JSON Array containing the cell data as items:
[ ["Value 1", "Value 2", "Value 3"], ["Value 4", "Value 5", "Value 6"], ["Value 7", "Value 8", "Value 9"], ]
The “root array” contains a list of arrays where each item of it represents the row with the values to be rendered in the list similar to this example structure:
Value 1 | Value 2 | Value 3 |
Value 4 | Value 5 | Value 6 |
Value 7 | Value 8 | Value 9 |
The JSON array format is the most effective format and should be preferred whenever possible.
JSON Object row items
Another option for the list input model could be to contain a list of JSON Objects whereas the name of the object fields correspond to the column headers and the value will become the cell value:
[ { "make": "Mercedes", "maxspeed": "250", "type": "S300" }, { "make": "BMW", "maxspeed": "260", "type": "530" }, { "make": "Tesla", "maxspeed": "260", "type": "ModelS" } ]
Each JSON object in the array will finally be rendered as a single row to the list as this example shows:
Mercedes | 250 | S300 |
BMW | 260 | 530 |
Tesla | 260 | ModelS |
Since this input data format is very “noisy” (it repeats the attribute names for each single cell), you should prefer the nested array format whenever possible since it is less complex, smaller in size and therefore better performing.
List Schema (JSON Schema)
FEATURE TAG: F-M6JK
The List Schema is a JSON Schema file which defines the structure of each row item. Additionally it configures how the columns must be displayed in the list. It is a JSON document which follows the schema specification from json-schema.org.
The List Schema is typically located in the property store at $APP_HOME/$TYPE
whereas $APP_HOME
stands for the home path of your app and $TYPE
stands for the JSON object type your schema describes. For example: global/tld.domain.myapp/schema/person
.
Here is a simple example of a person object described in a List Schema (JSON Schema):
{ "type": "object", "properties": { "firstName": { "title": "First Name", "type": "string", "description": "This is the first name of the person." }, "lastName": { "title": "Last Name", "type": "string", "description": "This is the last name of the person." }, "age": { "title": "Age", "type": "number", "description": "This is the age of the person." }, "gender": { "title": "Gender", "type": "string", "description": "This is the gender of the person.", "enum": ["Female", "Male"] } } }
Each properties
entry in this JSON Schema defines a single column in the list. The title
of a property will be used as the caption for a column. Therefore, finally we expect to have a table with these columns and headers:
First Name | Last Name | Age | Gender |
---|---|---|---|
The List Schema describes the row item of the source JSON Array. This way a Form Schema can also be used as a List Schema since they can define the same, shared entity. Both are based on the JSON Schema format.
Hide a column (hidden)
FEATURE TAG: F-VEY9
You can hide a column in the table by setting the custom attribute piStyle
with value "hidden": true
in the List Schema as this example shows:
{ "type": "object", "properties": { "firstName": { "type": "string", "piStyle": { "hidden": true } }, ... } }
Render cell content as link (href)
FEATURE TAG: F-XYLJ
In order to show the cell content as link, you have to set the attribute href
in piStyle
accordingly in the List Schema . See this example:
{ "type": "object", "properties": { "firstName": { "type": "string", "piStyle": { "href": "https://host/edit?name=${row.firstName}" } }, ... } }
You can use the variable ${row.<field>}
to access the value of a field of the current row by replacing <field>
with the id of the field those value to access.
Limit cell text
FEATURE TAG: F-XALK
In some situations it is necessary to limit the cell text to a max. length. For this you can use the piStyle
attribute maxLength
in the List Schema. Example:
... "description": { "type": "string", "title": "Description", "piStyle": { "maxLength": 3 } }, ...
This will limit the field to a max length of 3 chars plus dots:
Format cell text as badge
FEATURE TAG: F-BYTJ
You can format the text of a cell inside the List Schema and put it into a bage style using the badgeColor
attribute of the piStyle
element:
... "description": { "type": "string", "title": "Description", "piStyle": { "badgeColor": "#33ffad" } }, ...
This will format any cell text of the column Description into a badge. For example:
Nested Values
FEATURE TAG: F-SBBH
Until now we assumed that the input data is always a JSON array with flat primitive types like string or number:
[ ["A1", "A2", "A3"], ["B1", "B2", "B3"] ]
But it is also possible, this array is a mixture of primitive values and objects (= nested value) like this example shows:
[ [0, "Smith Max", {"order": {"orderId": "123", "orderDate": "01/01/25"}}], [1, "Mayer Sarah", {"order": {"orderId": "124", "orderDate": "01/02/25"}}], [2, "Foley Dave", {"order": {"orderId": "125", "orderDate": "01/03/25"}}] ]
Nested values are, by default, displayed in JSON format in the list:
And here is the List Schema which is used for this example:
{ "type": "object", "properties": { "id": { "type": "number", "title": "Customer ID" }, "name": { "type": "string", "title": "Customer Name" }, "order": { "type": "object", "title": "Order", "properties": { "orderId": { "type": "number" }, "orderDate": { "type": "string" } } } } }
Label Path
You can specify a single item out of the nested object which should be displayed as label using the piStyle
attribute labelPath
. For example:
... "order": { "type": "object", "title": "Order", "piStyle": { "labelPath": "orderId" }, "properties": { "orderId": { "type": "number" }, "orderDate": { "type": "string" } } } ...
The labelPath
defines the field inside the nested value which should be displayed in the list. All other fields of the nested value in the Details column wont be shown:
It is also possible to point to deeper nested paths for the label. For example:
"labelPath": "level1.level2.level"
Nested Label Badge
In order to format a nested label using a badge, you can again use the badgeColor
attribute:
... "order": { "type": "object", "title": "Order", "piStyle": { "labelPath": "orderId", "badgeColor": "#33ffad" }, "properties": { "orderId": { "type": "number" }, "orderDate": { "type": "string" } } } ...
This will render the label in the Details column like this example:
Make label clickable / Show details
You can make the label clickable. If the user click such a label, then a view is rendered which shows the nested value as JSON. To do so add the attributes contentType: "application/json"
and "openInViewerOnClick": true
to the List Schema:
... "order": { "type": "object", "title": "Order", "piStyle": { "labelPath": "orderId", "badgeColor": "#33ffad", "contentType": "application/json", "openInViewerOnClick": true }, "properties": { "orderId": { "type": "number" }, "orderDate": { "type": "string" } } } ...
If the user clicks on the label, a view opens with a JSON tree inside:
Use fixed label text
FEATURE TAG: F-FCLJ
Instead of a dynamic value from the nested item, also a fixed text can be shown as label by using the attribute fixedText
:
... "order": { "type": "object", "title": "Order", "piStyle": { "labelPath": "orderId", "badgeColor": "#33ffad", "fixedText": "Click here..." }, "properties": { "orderId": { "type": "number" }, "orderDate": { "type": "string" } } } ...
This will render the same, fixed text Click here...
on each cell:
Array of nested values
FEATURE TAG: F-O7BW
It is also possible to have a list of nested values to be displayed in a single cell.
See the Orders column in this example. There are multiple orders per cell:
This example uses an input JSON like this:
[ [ 0, "Smith Max", [ {"order": {"orderId": "123", "orderDate": "01/01/25"}}, {"order": {"orderId": "321", "orderDate": "23/08/25"}}, {"order": {"orderId": "543", "orderDate": "01/10/25"}} ] ], [ 1, "Mayer Sarah", [ {"order": {"orderId": "124", "orderDate": "01/02/25"}} ] ], [ 2, "Foley Dave", [ {"order": {"orderId": "125", "orderDate": "01/03/25"}} ] ] ]
.. and a List Schema like this, with the piStyle
settings from above:
{ "type": "object", "properties": { "id": { "type": "number", "title": "Customer ID" }, "name": { "type": "string", "title": "Customer Name" }, "orders": { "type": "array", "title": "Orders", "piStyle": { "labelPath": "order.orderId", "badgeColor": "#33ffad", "openInViewerOnClick": true, "contentType": "application/json" }, "items": { "order": { "type": "object", "properties": { "orderId": { "type": "number" }, "orderDate": { "type": "string" } } } } } } }
Note that the orders type
is now array
since it can contain multiple order entries.
List Config
FEATURE TAG: F-KQO6
The List Config is the main configuration file for a list. Here you can define the title and description of the list and from where the schema and input data should be loaded from.
In order to create a new list and show it as a new entry in your apps tiles listing, you have to add the list config as a new property to this path in the property store:
global/app/tld.domain.myapp/list/<listname>
Whereas <listname>
is a lower-case and unique name for the list in a format supported by the property path.
The content type of the list config property must be application/json; type=list
.
You can also create it in the web ui in section LOW CODE -> Properties
using the List Config wizard.
The most basic structure of this JSON document must be like this:
{ "title": "The title of the list", "description": "The description of the list", "input": "$uri:command:property.value.list?pattern=global/app/../*", "schema": "$uri:property:global/app/path/to/my-list-schema", }
title
The title
defines the title of the list to be displayed in the web ui and in other locations.
This can be a static text or an i18n uri like $uri:i18n:myTitle
for example. See: Internationalization (i18n)
description
The description
is optional and describes the intention of the list.
This can be a static text or an i18n uri like $uri:i18n:myDescription
for example. See: Internationalization (i18n)
icon
Each list can have an individual icon. This value must be a code name from the Google Material Icons as listed here: https://fonts.google.com/icons
This value is optional. If not given, the icon with code name ballot
is shown as default:
color
The color of the list icon. If not specified, the secondary color is used, which depends on the style settings. By default this is a blue color like this:
The color value can be a hexadecimal value, like this:"color": "#FFA500"
Or it can be a constant selected from the Colors palette.
For example you could use the color palette constant “green” like this:"color": "green"
Another example:
"color": "red-8"
hidden
This optional attribute defines whether the list should be shown as tile inside the app ("hidden": false
) or not ("hidden": true
).
The list can be still loaded by its URL if required.
If this attribute is missing, "hidden": false
is used as a default.
schema
A PIPEFORCE URI pointing to the location where to load the List Schema from.
For example, the attribute could look like this:
"schema": "$uri:property:global/app/tld.domain.myapp/schema/person"
Which could return a JSON schema like this:
{ "type": "object", "properties": { "firstName": { "type": "string", "title": "First Name", "description": "The first name of the person." }, "lastName": { "type": "string", "title": "Last Name", "description": "The last name of the person." }, "age": { "type": "number", "title": "Age", "description": "The age of the person." }, "gender": { "type": "string", "title": "Gender", "description": "The gender of the person.", "enum": [ "male", "female", "neutral" ] }, "birthDate": { "type": "string", "format": "date", "title": "Date of birth", "description": "The date of birth of the person." } } }
input
A PIPEFORCE URI pointing to the location where to load the input model from (must be a JSON array whereas each item represents a single row in the table).
For example, the input parameter could look like this in order to load a JSON property from the property store:
"input": "$uri:property:global/app/tld.domain.myapp/data/person/fe97df"
Which could return a list data (= model) as JSON array similar to this:
[ ["Sam", "Smith", 38, "male", "04/04/1989"], ["Marisha", "Mayer", 42, "female", "03/08/1979"], ["Lee", "Long", 55, "male", "01/02/1968"] ]
Make a list row editable
SINCE VERSION 9.0 FEATURE TAG: F-KL7AP
It is possible to combine the List Framework with the Forms Framework so a row in a list becomes editable: After such a row was clicked, a modal form opens with all row data in it. The row data can be changed and after submit, the data will be updated in the list. For example:
Let’s see here how this can be configured…
Step 1: Change List Config
Let’s assume you have a List Config like this at path global/app/io.pipeforce.myapp/list/person
:
{ "title": "person", "description": "", "schema": "$uri:property:global/app/io.pipeforce.t169/schema/person", "input": "$uri:command:property.value.list?pattern=global/app/io.pipeforce.t169/data/person/*" }
This List Config needs to be enriched with information about the form to edit a row. To do so, add the attributes formConfig
, formInputCol
and formOutputCol
to the List Config as as shown in this example:
{ "title": "person", "description": "", "schema": "$uri:property:global/app/io.pipeforce.t169/schema/person", "input": "$uri:command:property.value.list?pattern=global/app/io.pipeforce.t169/data/person/*", "formConfig": "global/app/io.pipeforce.t169/form/person", "formInputCol": "readPath", "formOutputCol": "writePath" }
formConfig
: This is the path of the property which defines the Form Config for the row form. This is a standard Form Config as described in the Forms Framework. So you can use all the features of the forms framework here. Note that the Form Schema and List Schema here are sharing the same structure so both pointing to the same schema property as defined in the List Config.formInputCol
: This is the id of the column which contains the (hidden) client side PIPEFORCE URI to load the input data for the form so it can be edited.formOutputCol
: This is the id of the column which contains the (hidden) client side PIPEFORCE URI to store the form data to afterSUBMIT
was clicked in the row form.
Step 2: Change List Schema
The next step is to change the List Schema and add three more columns:
A column which contains the unique id of a row to load and save it.
A column which contains the path how to load the row data from backend. Relates to
formInputCol
.A column which contains the path how to save the row data to backend. Relates to
formOutputCol
.
Let’s assume you already have List Schema like this example:
{ "type": "object", "properties": { "firstName": { "type": "string", "title": "First Name", "description": "The last name of the person." }, "lastName": { "type": "string", "title": "Last Name", "description": "The last name of the person." }, "age": { "type": "number", "title": "Age", "description": "The age of the person." }, "gender": { "type": "string", "title": "Gender", "description": "The gender of the person.", "enum": [ "male", "female", "neutral" ] }, "birthDate": { "type": "string", "format": "date", "title": "Date of birth", "description": "The date of birth of the person." } } }
Now lets add three more top level fields uuid
, readPath
, writePath
to this schema as this example shows at its bottom:
{ "type": "object", "properties": { "firstName": { "type": "string", "title": "First Name", "description": "The last name of the person." }, "lastName": { "type": "string", "title": "Last Name", "description": "The last name of the person." }, "age": { "type": "number", "title": "Age", "description": "The age of the person." }, "gender": { "type": "string", "title": "Gender", "description": "The gender of the person.", "enum": [ "male", "female", "neutral" ] }, "birthDate": { "type": "string", "format": "date", "title": "Date of birth", "description": "The date of birth of the person." }, "uuid": { "type": "string", "title": "Primary key", "description": "Unique Number for updating record. Primary key.", "piStyle": { "hidden": true } }, "readPath": { "type": "string", "piStyle": { "hidden": true, "value": "$uri:property:global/app/io.pipeforce.t169/data/person/${row.uuid}" } }, "writePath": { "type": "string", "piStyle": { "hidden": true, "value": "$uri:property:global/app/io.pipeforce.t169/data/person/${row.uuid}" } } } }
The field uuid
contains an id in order to point to a row data set. In this example, this uuid is returned from the backend. But it can be any field capable to identify a single row entry data set. This column is set to "hidden": true
in order to not show it in the list.
The added fields readPath
and writePath
correspond with the value set in the List Config using the attributes formInputCol
and formOutputCol
. The value of these fields can be any client side PIPEFORCE URI pointing to a location where the row data can be loaded from and written to. This can differ for any row if required.
Additionally the piStyle
attribute is used inside readPath
and writePath
:
hidden
: Set totrue
in order to not show the value of this column in the list.value
: Since the backend isn’t returning any value for this field, a fixed value for this column from client side is set here. The keyword${row.uuid}
is a special variable which will be rendered at client side for any row and in this case it will be replaced by the value of theuuid
column of the current row. This way you can set the uuid of the current row in order to read and write it in the form. You can refer to any column field here by its id in order to construct a valid client side PIPEFORCE URI required to read and write row data. For example in order to refer to thefirstName
field, you would use${row.firstName}
instead.
Step 3: Create the row edit form
As last step you have to create the form which should be displayed in case a row has been clicked.
Go to LOW CODE -> Properties
.
Select your app and create a new Form Config
.
Let’s assume you have a Form Config like this:
{ "id": "person", "title": "person", "public": false, "description": "", "schema": "$uri:property:global/app/io.pipeforce.t169/schema/person", "output": "$uri:property:global/app/io.pipeforce.t169/data/person/#{property.uuid}", }
Additionally, we need to change this so that the fields uuid
, readPath
and writePath
are not shown in this form. To do so, add the layout
section to this Form Config as shown in this example:
{ "id": "person", "title": "person", "public": false, "description": "", "schema": "$uri:property:global/app/io.pipeforce.t169/schema/person", "output": "$uri:property:global/app/io.pipeforce.t169/data/person/#{property.uuid}", "layout":{ "items":[ {"field": "firstName"}, {"field": "lastName"}, {"field": "birthDate"}, {"field": "age"}, {"field": "gender"} ] } }
The layout
part explicitly defines the fields which must be displayed in the form. All other fields like uuid
, readPath
and writePath
are ignored and not displayed.
Note: Any input
and output
settings in this Form Config can be ignored since they will be overwritten by the row edit form using values from readPath
and writePath
instead.
Done. If you open your list and click a row, the form will load the current row data by using the PIPEFORCE URI of column readPath
and shows this data in a form defined by the form config. On SUBMIT it will use the PIPEFORCE URI of column writePath
to store changed data back to the backend.
Add Comment