Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Table of Contents
minLevel1
maxLevel3
outlinefalse
typelist
printablefalse

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.

...

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.

...

Info

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.

...

  • 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 command property.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.

...

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:

...

Info
  • 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:

...

Info

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:

...

Info

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)

Status
colourPurple
titleFeature TAG: F-M6JK

...

Info

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)

Status
colourPurple
titleFeature TAG: F-VEY9

...

Code Block
languagejson
{
    "type": "object",
    "properties": {
        "firstName": {
            "type": "string",
            "piStyle": {
              "hidden": true
            }
        },
        ...
    }
}

Render cell content as link (href)

Status
colourPurple
titleFEATURE TAG: F-XYLJ

...

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

Status
colourPurple
titleFEATURE TAG: F-XALK

...

This will limit the field to a max length of 3 chars plus dots:

...

Format cell text as badge

Status
colourPurple
titleFEATURE TAG: F-BYTJ

...

This will format any cell text of the column Description into a badge. For example:

...

Nested Values

Status
colourPurple
titleFeature TAG: F-SBBH

...

Code Block
languagejson
{
  "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:

...

Code Block
"labelPath": "level1.level2.level"

Nested Label Badge

In order to format a nested label using a badge, you can again use the badgeColor attribute:

...

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:

...

If the user clicks on the label, a view opens with a JSON tree inside:

...

Use fixed label text

Status
colourPurple
titleFEATURE TAG: F-FCLJ

...

This will render the same, fixed text Click here... on each cell:

...

Array of nested values

Status
colourPurple
titleFEATURE TAG: F-O7BW

...

Info

Note that the orders type is now array since it can contain multiple order entries.

List Config

Status
colourPurple
titleFeature TAG: F-KQO6

...

Code Block
languagejson
{
  "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 default 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:

...

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

...

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.

...

Code Block
{
  "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).

...

Code Block
[
  ["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

Status
colourBlue
titleSince Version 9.0
Status
colourPurple
titleFeature Tag: F-KL7AP

...

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:

...

  • 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 after SUBMIT 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:

...

  • hidden: Set to true 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 the uuid 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 the firstName 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.

...

Code Block
languagejson
{
  "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:

Code Block
languagejson
{
  "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"}
    ]
  }
}

...

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.

Internationalization (i18n)

Text elements of the list can also be internationalized (= translated to different languages). See here for more details, how this works: Internationalization (i18n).

Serverside search, pagination and filtering

By default the items of a list are loaded as a whole. For a huge amount of data, this approach is not sufficient since it will probably slow-down browser performance drastically. In this case a pagination approach is more efficient where the list is split into pages and each page shows only a certain subset of the overall data items. The user can navigate forward and backward on these pages then.

Here is an example of a list with pagination support:

...

Enable serverside pagination

In order to enable serverside pagination, you have to adjust your list config and set the parameter search to serverside like this example shows:

Code Block
{
  "title": "My List",
  "description": "This is my list",
  "input": "...",
  "search": "serverside",
  "schema": "..."
}

After “search”: “serverside” was set and the list config was saved, the default behaviour of the list changes as follows:

  • Column sorting is (currently) not supported. The result is sorted by the creation date.

  • When user types in a search value, the search string is passed as a variable to the input string (see below).

  • Pagination is enabled with 20 items per page.

Input variables

Since the input parameter of the list configuration can contain any PIPEFORCE URI string, there must be a way to set the user selected search and pagination parameters on such a string.

To do so, you can use input variables. The client will automatically replace those variables by the user selected value and sets those values on the input URI:

  • ${searchString}
    The search string as it has been typed-in by the user in the search text box.

  • ${maxResults}
    The number of results to be returned per server query. Is currently fixed to 20.

  • ${offset}
    The 0-based offset in the search result (for pagination). For example if the overall size of the search result is 100, then an offset of 19 would return the next 20 results and so on.

Here is an example, how to use these input variables in an input URI inside a list config:

Code Block
languagejson
{
  ...
  "search": "serverside",
  "input": "...?search=${searchString}&max=${maxResults}&offset=${offset}",
  ...
}

As you can see, the input URI contains the variables ${searchString}, ${maxResults} and ${offset} in the query part, which the client will replace by the concrete user value before the input URI will be executed. So for example lets assume, the user searches for foo, wants to display a maximum of 20 items per page and starts at the very beginning, on first page, then the request query with finally interpolated (replaced) variables could look like this:

Code Block
...?search=foo&max=20&offset=0

This way you have the full flexibility which URI to use. It can be a command, a pipeline or even an external system for example. As long as the endpoint supports a search string, max and offset and returns a response format as mentioned below, you can use it for pagination and server side searches.

Response format

Every URI endpoint specified by the input parameter in the list config must return a JSON with exactly this format so the UI can handle it:

Code Block
languagejson
{
  "searchResult": [...],
  "overallResultCount": 1234,
  "offset": 0,
  "maxResults": 20,
  "duration": 123,
  "keyFilter": "",
  "valueFilter": "",
  "typeFilter": "",
  "numberOfPages": 2,
  "currentPage": 1,
  }

The parameters are:

  • searchResult
    Contains the search result payload of this search call which is a subset of the overall search results starting at index offset and ends at index offset + maxResults.

  • overallResultCount
    The overall number of matching results of this search found in database.

  • currentResultCount
    The number of results in the current response.

  • offset
    The 0-based offset used in this result.

  • maxResults
    The max. number of results returned per response.

  • duration
    The time in millis, this search took at server side.

  • keyFilter
    A property key filter used in this search to search only in properties matching this key.

  • valueFilter
    The search string used in this search to search in the properties value.

  • typeFilter
    The content type used in the search to shrink down the matching properties to search inside.

  • numberOfPages
    The overall number of pages required to list all results depending on given maxResults.

  • currentPage
    The 1-based index of the current page depending on given page parameter or automatically calculated by given offset.

Filter Fields

Serverside search optionally supports filter fields so the user can select and pass multiple filter conditions to the backend.

In order to enable filters, you have to adjust the list config and define the required filter fields, like this example shows:

Code Block
languagejson
{
  "title": "My List",
  "schema": "...",
  "search": "serverside",
  "filter": [
    {
      "title": "First Name",
      "columnName": ["firstName"],
      "type": "text"
    },
    {
      "title": "Last Name",
      "columnName": ["lastName"],
      "type": "text"
    },
    {
      "title": "Age",
      "columnName": ["age"],
      "type": "text"
    },
    {
      "title": "Date of Birth",
      "columnName": ["birthDate"],
      "type": "dateRange"
    },
    {
      "title": "Gender",
      "columnName": ["gender"],
      "type": "selection",
      "multiple": true,
      "option": ["male", "female"]
    }
  ],
  "pagination": {
    "allow": true
  }
}

Each filter field has has these parameters:

  • title
    The title of the filter to be displayed on the UI.

  • columnName
    The name of the column in the list, this filter must be applied on.

  • type
    The type of filter. Defines, how the filter field gets displayed on the UI. Can be one of:

    • text
      Free text. Will be rendered as text field.

    • dateRange
      Will be rendered as a date range picker field.

    • selection
      Will be rendered as a dropdown field.

      • If multiple is set to true = Allows multiselection in the dropdown.

      • If multiple is not set or false = Allows only single seletion in the dropdown.

      • option
        An array of options which can be selected from the dropdown.

The filter fields will be listed on top of the list. Once the user has specified a filter value, the list will be reloaded with the specified filter value passed to the endpoint.

Info

In case no input parameter is specified, by default a property search on JSON documents is executed using the command property.json.search. The search filter conditions for this command are created automatically at client side based on the filter fields used by the user. No further settings required.

Passing filter values as input variables to custom input URI

In case a custom input URI is defined, the selected filter values must be passed to the endpoint. This is done again using variable interpolation wheras the name of the variable must be the name of the column, the filter was applied to. Here is an example:

Code Block
languagejson
{
  ...
  "input": "$uri:pipeline:...?name=${firstName}",
  "search": "serverside",
  "filter": [
    {
      "title": "First Name",
      "columnName": ["firstName"],
      "type": "text"
    }
  ]
}

In case the user has set Mustermann in filter First Name, then the final URI will be like this for example:

Code Block
$uri:pipeline:...?name=Mustermann

The value is automatically URL encoded.

In case of a filter of type selection with multiple set to true, multiple values are passed as… TODO

In case of a filter of type dateRange, the selected value will be passed as… TODO

Using a pipeline as custom input endpoint

In case you would like to call a custom pipeline in the input, you can do so by setting the $uri:pipeline URI like this example shows:

Code Block
languagejson
{
  ...
  "input": "$uri:pipeline:global/app/myapp/pipeline/mypipeline?name=${firstName}",
  "search": "serverside",
  "filter": [
    {
      "title": "First Name",
      "columnName": ["firstName"],
      "type": "text"
    }
  ]
}

Any filter input variable / request query parameter will be available as vars inside the pipeline. This is also true for any pagination variable. For example:

Code Block
languageyaml
pipeline:
  - log: "The passed name is: ${vars.name}"
  ... Do the search and filter logic here ...
  - body.set: |
      {
        "searchResult": [...],
        "overallResultCount": 1234,
        "offset": 0,
        "maxResults": 20,
        "duration": 123,
        "keyFilter": "",
        "valueFilter": "",
        "typeFilter": "",
        "numberOfPages": 2,
        "currentPage": 1,
      }
Info

Make sure the pipeline returns a JSON structure as required by the client in order to show the list items correctly.

Using property.json.search as custom input endpoint

In order to simplify the JSON search in the property store, you can use the command property.json.search and customize its parameters to fit your special needs. This command already returns the required format without the need of further transformation. Here is an example how to use and customize it inside a pipeline with input variables passed from the client:

Code Block
languagejson
pipeline:

  - property.json.search:
      pattern: "global/app/foo.bar.myapp/data/person/*"
      max: ${vars.max}
      offset: ${vars.offset}
      condition: |
        { "and": [{ "field": "*", "op": "icontains", "value": "${vars.name}" }] }