List Values

Last update: Edit

1 Introduction

ListValue is used to represent a list of objects for the datasource property. Corresponding list item values represent properties of different types linked to a datasource property.

2 ListValue

When a datasource property with isList="true" is configured for a widget, the client component gets a list of objects represented as a ListValue. This type allows detailed access and control over the data source.

export interface ObjectItem {
    id: GUID;
}

export interface ListValue {
    status: ValueStatus;

    offset: number;
    limit: number;
    setOffset(offset: number): void;
    setLimit(limit: Option<number>): void;
    requestTotalCount(needTotalCount: boolean): void;
    hasMoreItems?: boolean;
    totalCount?: number;
    items?: ObjectItem[];

    sortOrder: SortInstruction[];
    filter: Option<FilterCondition>;
    setSortOrder(sortOrder: Option<SortInstruction[]>): void;
    setFilter(filter: Option<FilterCondition>): void;
}

2.1 Pagination

The offset and limit properties specify the range of objects retrieved from the datasource. The offset is the starting index and the limit is the number of requested items. By default, the offset is 0 and the limit is undefined which means all the datasource’s items are requested. You can control these properties with the setOffset and setLimit methods. This allows a widget to not show all data at once. Instead it can show only a single page when you set the proper offset and limit, or the widget will load additional data whenever it is needed if you increase the limit.

The following code sample sets the offset and limit to load datasource items for a specific range:

this.props.myDataSource.setOffset(20);
this.props.myDataSource.setLimit(10);

You can use the setOffset and setLimit methods to create a widget with pagination support (assuming widget properties are configured as follows):

interface MyListWidgetsProps {
    myDataSource: ListValue;
    pageSize: number;
}

To set the number of items requested by the datasource you can use the setLimit in the constructor of the widget:

export default class PagedWidget extends Component<PagedWidgetProps> {
    constructor(props: PagedWidgetProps) {
        super(props);

        props.myDataSource.setLimit(props.pageSize);
    }
}

To switch to a different page you can change the offset with the setOffset method:

const ds = this.props.myDataSource;
const current = this.props.myDataSource.offset;
<button onClick={() => ds.setOffset(current - this.props.pageSize)}>
    Previous
</button>
<button onClick={() => ds.setOffset(current + this.props.pageSize)}>
    Next
</button>

The hasMoreItems property indicates whether there are more objects beyond the limit of the most recent list. When a widget does not show all the records immediately by setting a limit with setLimit and allows the user to load additional data, you can use this property to make it clear in the user interface that the user has reached the end of the list.

The following code sample shows a ‘load more’ button only when there is more data available, and loads additional data when the user clicks the button:

const currentLimit = this.props.myDataSource.limit;
this.props.myDataSource.hasMoreItems &&
<button 
    onClick={() => this.props.myDataSource.setLimit(currentLimit + 10)}
>
    Load more
</button>

When a limit is set to 0, that case is handled in a special way. In this case ListValue will avoid sending a request to the server to retrieve data and will immediately return an empty result. This property can be used to build widgets that load their data “lazily”: only when and if a specific condition is met.

The following code sample loads the data only if a button is pressed:

export default const LazyWidget = (props: LazyWidgetProps) => {
    useMemo(() => props.myDataSource.setLimit(0), []);
    return props.myDataSource.items?.length ? (
        props.myDataSource.items.map((i) => <div key={i.id}>Item</div>)
    ) : (
        <button onClick={() => props.myDataSource.setLimit(undefined)}>Load data</button>
    );
}

The totalCount property is the total number of objects the datasource can return. Calculating a total count might consume significant resources and is only returned when the widget indicated needs a total count by calling the requestTotalCount(true) method. When possible, please use the hasMoreItems instead of the totalCount property.

The following code sample shows how to request the total count to be returned:

export default class PagedWidget extends Component<PagedWidgetProps> {
    constructor(props: PagedWidgetProps) {
        super(props);
    
        props.myDataSource.requestTotalCount(true);
    }
}

The setOffset and setLimit are supported on all data sources. For the XPath and Database data sources, only the requested page is returned to the client. For other data sources the full set is returned to the client, but the widget will only receive the requested page in the items property.

2.3 Sorting

It is possible to set a specific sort order for items in the list using setSortOrder method and get the current sort order via sortOrder field. When a new sort order is set, widget will receive new results on next re-render.

setSortOrder method accepts one argument which should be an array of SortInstructions. Also undefined could be passed to setSortOrder to restore default sort order.

SortInstruction is defined as an array of two elements:

type SortInstruction = [id: ListAttributeId, dir: SortDirection];

The first element of SortInstruction type is id of an attribute property linked to the datasource. This lets the widget specify which attribute should be used for sorting. Though not every attribute could be used for sorting, for some attributes sorting may be disallowed. To determine if an attribute could be used for sorting sortable flag of its attribute property has to be checked. This flag specifies if it is possible to use particular attribute for sorting. See Attribute ID, Sortable and Filterable Flags section for more information about attribute id and sortable flag.

The second attribute of SortInstruction type is a literal string representing the sort direction, either "asc" or "desc".

The following code examples show how to apply sorting to the property myDataSource based on linked attributes attributeAge and attributeName:

interface MyListWidgetsProps {
    myDataSource: ListValue;
    attributeAge: ListAttributeValue<BigJS>; // Integer
    attributeName: ListAttributeValue<BigJS>; // String
}

Set ascending sort order based on attribute represented by attributeAge property with the following code:

if (this.props.attributeAge.sortable) {
    // sort by age ascending
    const sortInstrs = [
        [this.props.attributeAge.id, "asc"]
    ]; 
    this.props.myDataSource.setSortOrder(sortInstrs);
} else {
    console.warn("Can't apply sorting. Attribute Age is not sortable");
}

The following code sample shows how to sort on multiple attributes at the same time:

if (this.props.attributeAge.sortable && this.props.attributeName.sortable) {
    // sort by age descending, and then by name ascending (within age groups)
    const sortInstrs = [
        [this.props.attributeAge.id, "desc"],
        [this.props.attributeName.id, "asc"],
    ]; 
    this.props.myDataSource.setSortOrder(sortInstrs);
} else {
    console.warn("Can't apply sorting. Attribute Age and/or Name is not sortable");
}

Reset to default sort order by passing undefined as the following code shows:

this.props.myDataSource.setSortOrder(undefined);

The setSort method is supported for all data sources. For Database and XPath data sources the sorting is done by the back end. For all the other data sources the sorting is done by the client.

2.4 Filtering

It is possible to set filtering conditions for items of a datasource. setFilter() method accepts filter conditions and applies filtering. filter field represents the current filter condition.

setFilter accepts only a specially created object of type FilterCondition that describes desired filtering behavior. It is possible to create a filter conditions object using functions provided in mendix module under mendix/filters/builders path. Those functions we call filter builders. Also undefined could be passed to setFilter to clear filtering conditions.

Some examples of builder functions are equals, greaterThan, lessThanOrEqual for filtering on DateTime or Decimal attributes. Functions like startsWith, contains are useful for filtering on String attributes.

The following code samples show how to use filter builders and apply filtering to a data source property with three linked attributes:

interface MyListWidgetsProps {
    myDataSource: ListValue;
    myAttributeString: ListAttributeValue<string>;
    myAttributeBoolean: ListAttributeValue<boolean>;
    myAttributeNumber: ListAttributeValue<BigJS>;
}

The setFilter method is supported for all data sources. For Database and XPath data sources the filtering is done by the back end. For all the other data sources the filtering is done by the client. In both cases the widget will receive the filtered items in the items property.

2.4.1 Simple Filtering

To apply a simple filter based on the value of an attribute represented by myAttributeString property the following code may be used:

import { attribute, literal, startsWith } from "mendix/filters/builders";

// in the widget code
if (this.props.myAttributeString.filterable) {
    const filterCond = startsWith(attribute(this.props.myAttributeString.id), literal("B"));
    this.props.myDataSource.setFilter(filterCond);
} else {
    console.log("Attribute is not filterable");
}

First step the code takes is checking for the possibility to use filtering on myAttributeString property by checking filterable flag. Then filterCond filter condition is constructed which specifies that attribute represented by myAttributeString should start with character "B". setFilter call applies the filter, and on the next re-render the component gets only items where the value of an attribute represented by property myAttributeString is starting with "B".

Similarly, to apply a condition where the value on an attribute represented by myAttributeBoolean property is set to true:

import { attribute, literal, equals } from "mendix/filters/builders";

// in the widget code
if (this.props.myAttributeBoolean.filterable) {
    const filterCond = equals(attribute(this.props.myAttributeBoolean.id), literal(true));
    this.props.myDataSource.setFilter(filterCond);
} else {
    console.log("Attribute is not filterable");
}

The following code sample shows how to remove current filtering condition:

this.props.myDataSource.setFilter(undefined);

2.4.2 Advanced Filtering

In some use cases it is necessary to apply more complex filtering conditions. For example if a use case requires fetching only items where myAttributeString starts with "B" and myAttributeBoolean is set to true, or items where myAttributeNumber is greater than 10. In order to construct such condition special filter builders and and or have to be used. The following code sample shows how to use them. Note that check for filterable flags omitted for simplicity. Real widgets should always take filterable flag into account.

import { attribute, literal, startsWith, equals, greaterThan, and, or } from "mendix/filters/builders";

// in the widget code
if (/* check that all properties are filterable */) {
    const filterCond = or(
        and(
            startsWith(attribute(this.props.myAttributeString.id), literal("B")),
            equals(attribute(this.props.myAttributeBoolean.id), literal(true))
        ),
        greaterThan(attribute(his.props.myAttributeNumber.id), literal(10))
    );
    this.props.myDataSource.setFilter(filterCond);
} else {
    console.log("Some attribute is not filterable");
}

2.5 Working With Actual Data

The items property contains all the requested data items of the datasource. However, it is not possible to access domain data directly from ListValue, as every object is represented only by GUID in the items array. Instead, a list of items may be used in combination with other properties, for example with a property of type attribute, action, or widgets. See the next section for detailed information about working with different property types in combination with ListValue.

2.6 View State

View state is a mechanism of storing the current state of a page when user navigates away from the page and restoring that state when user navigates back to the page. For example user has some sorting order applied in a DataGrid widget on an overview page and navigates away to a detail page. When user gets back to the overview page, the DataGrid widget will be initialized with previously used sorting order.

View state works transparently for a widget, no additional steps needed from the widget in order to benefit from view state mechanism.

The following information of a ListView is getting automatically stored and restored: - pagination state (limit and offset fields) - sorting state (sortOrder field) - filtering state (filter field)

3 Linked Property Values

3.1 ListActionValue

ListActionValue represents action that may be applied to items from ListValue. The ListActionValue is an object and its definition is as follows:

export interface ListActionValue {
    get: (item: ObjectItem) => ActionValue;
}

In order to call an action on a particular item of a ListValue first an instance of ActionValue should be obtained by calling ListActionValue.get with the item (assuming widget properties are configured as follows):

interface MyListWidgetsProps {
    myDataSource: ListValue;
    myListAction: ListActionValue;
}

The following code sample shows how to call myListAction on the first element from the myDataSource.

const actionOnFirstItem = this.props.myDataSource.myListAction.get(this.props.myDataSource.item[0]);

actionOnFirstItem.execute();

In this code sample, checks of status myDataSource and availability of items are omitted for simplicity. See the ActionValue section for more information about the usage of ActionValue.

3.2 ListAttributeValue

ListAttributeValue represents an attribute property that is linked to a data source. This allows the client component to access attribute values on individual items from a ListValue. ListAttributeValue is an object and its definition is as follows:

export interface ListAttributeValue<T extends AttributeValue> {
    get: (item: ObjectItem) => EditableValue<T>; // NOTE: EditableValue obtained from ListAttributeValue always readonly

    id: ListAttributeId;
    sortable: boolean;
    filterable: boolean;

    type: AttributeType;

    formatter: ValueFormatter<T>;
    universe: Option<T[]>; // only for attributes of type Enumeration
}

3.2.1 Obtaining Attribute Value

In order to work with the attribute value of a particular item of a ListValue first an instance of EditableValue<T> should be obtained by calling ListAttributeValue.get with the item. The type <T> depends on the allowed value types as configured for the attribute property.

Let’s take a look at some example. Assuming widget properties are configured as follows with myAttributeOnDatasource property allowing attribute of type string:

interface MyListWidgetsProps {
    myDataSource: ListValue;
    myAttributeOnDatasource: ListAttributeValue<string>;
}

The following code sample shows how to get an EditableValue<string> that represents a read-only value of an attribute of the first element from the myDataSource.

const attributeValue = this.props.myAttributeOnDatasource.get(this.props.myDataSource.items[0]);

Note: in this code sample checks of status of myDataSource and availability of items are omitted for simplicity. See EditableValue section for more information about usage of EditableValue.

3.2.2 Attribute ID, Sortable and Filterable Flags

id field of type ListAttributeId represents the unique randomly generated string identifier of an attribute. That identifier could be used when applying sorting and filtering on a linked data source property to identify which attribute should be used for sorting and/or filtering. Check Sorting and Filtering sections for more information.

Fields sortable and filterable specify if the attribute could be used for sorting and/or filtering. Those flags have to be checked before a widget applies filtering or sorting on a data source property. An attempt to filter on a non-filterable attribute or sort on a non-sortable attribute would lead to an error during the execution time.

3.2.3 Attribute Type

Attribute property determines which attribute types could be configured for that property. For example a property may allow attributes of type String and Integer in order to present progress. While this is convenient for users it may require some additional work for a developer by processing different data types.

It is possible to determine type of attribute by checking type field on a property. The following code sample shows how to check the attribute type on the property named myAttributeOnDatasource:

if (this.props.myAttributeOnDatasource.type === "String") {
    console.log("String attribute");
} else if (this.props.myAttributeOnDatasource.type === "Integer") {
    console.log("Integer attribute");
} else {
    console.log("Not a String/Integer attribute");
}

3.2.4 Formatter and Universe

formatter field represents the default formatter that is going to be used on values obtained by get function.

Optional universe field represents an array of possible values of an attribute. See universe field of EditableValue for more information.

3.3 ListWidgetValue

ListWidgetValue represents a widget property that is linked to a data source. This allows the client component to render child widgets with items from a ListValue. ListWidgetValue is an object and its definition is as follows:

export interface ListWidgetValue {
    get: (item: ObjectItem) => ReactNode;
}

For clarity, consider the following example using ListValue together with the widgets property type. When the widgets property named myWidgets is configured to be tied to a datasource named myDataSource, the client component props appear as follows:

interface MyListWidgetsProps {
    myDataSource: ListValue;
    myWidgets: (i: ObjectItem) => ReactNode;
}

Because of the above configurations, the client component may render every instance of widgets with a specific item from the list like this:

this.props.myDataSource.items.map(i => this.props.myWidgets.get(i));

When the widgets property is not required, there may not be any child widgets configured. In that case the value of the widgets property will be undefined (as in the example abovemyWidgets).

3.4 ListExpressionValue

ListExpressionValue represents an expression property or text template property that is linked to a data source. This allows the client component to access expression or text template values for individual items from a ListValue. ListExpressionValue is an object and its definition is as follows:

export interface ListExpressionValue<T extends AttributeValue> {
    get: (item: ObjectItem) => DynamicValue<T>;
};

The type <T> depends on the return type as configured for the expression property. For a text template property, this type is always string.

In order to work with the expression or text template value of a particular item of a ListValue, first an instance of DynamicValue should be obtained by calling ListExpressionValue.get with the item (assuming widget properties are configured as follows with an expression of type boolean):

interface MyListWidgetsProps {
    myDataSource: ListValue;
    myExpressionOnDatasource: ListExpressionValue<boolean>;
    myTextTemplateOnDatasource: ListExpressionValue<string>;
}

The following code sample shows how to get a DynamicValue that represents the value of an expression for the first element from the myDataSource.

const expressionValue = this.props.myDataSource.myExpressionOnDatasource.get(this.props.myDataSource.item[0]);

4 Filter Helpers

4.1 Value Helpers

Two basic helpers that allow to represent attributes and literal values in filter conditions are attribute and literal helpers. When creating a filter condition, every attribute or literal value has to be wrapped with a corresponding helper.

4.1.1 Attribute

The attribute helper takes one argument of type ListAttributeId. See ListAttributeValue.

The following code sample shows how to apply attribute helper and use its result in constructing a filter condition:

const attrA = attribute(this.props.myAttributeA.id);
const filterCondition = equals(attrA, literal("Bob"));

Attribute types available for filtering: * Boolean * DateTime * AutoNumber * Integer * Long * Decimal * Enum * String * HashString

Attribute types not available for filtering: * Binary * EnumSet * ObjectReference * ObjectReferenceSet

4.1.2 Literal

The literal helper takes one argument. Accepted argument types are: * Boolean values for Boolean attribute types * String literals for String, HashString and Enumeration attribute types * BigJS numbers for AutoNumber, Integer, Long and Decimal attribute types * Date objects for DateTime attribute type * undefined for any attribute type

The following code sample shows how to use literal helper:

const falsy = literal(false); // for Boolean
const bob = literal("Bob"); // for String, HashString, Enumeration
const meaningOfLife = literal(new BigJS(42)); // for AutoNumber, Integer, Long, Decimal
const now = literal(new Date()); // for DateTime
const undef = literal(undefined);

4.2 Basic Helpers

4.2.1 Equals

The equals helper takes two arguments produced by Value helpers. Accepts attributes and literals of any type.

The following code sample shows how to use equals helper:

const attrA = attribute(this.props.myAttributeA.id);
const name = literal("Bob");

// filter keeps items where value equals "Bob"
const filterCondition = equals(attrA, name);

4.2.2 NotEqual

The notEqual helper takes two arguments produced by Value helpers. Accepts attributes and literals of any type.

The following code sample shows how to use notEqual helper:

const attrA = attribute(this.props.myAttributeA.id);
const name = literal("Bob");

// filter keeps items where value not equal to "Bob"
const filterCondition = notEqual(attrA, name);

4.2.3 GreaterThan

The greaterThan helper takes two arguments produced by Value helpers. Allows only String, HashString, Enumeration, AutoNumber, Integer, Long Decimal, DateTime attributes and their corresponding literals.

The following code sample shows how to use greaterThan helper:

const attr = attribute(this.props.myAttributeA.id);
const meaningOfLife = literal(new BigJS(42));

// filter keeps items where value is greater than 42
const filterCondition = greaterThan(attr, meaningOfLife);

4.2.4 LessThan

The lessThan helper takes two arguments produced by Value helpers. Allows only String, HashString, Enumeration, AutoNumber, Integer, Long Decimal, DateTime attributes and their corresponding literals.

The following code sample shows how to use lessThan helper:

const attr = attribute(this.props.myAttributeA.id);
const meaningOfLife = literal(new BigJS(42));

// filter keeps items where value is less than 42
const filterCondition = lessThan(attr, meaningOfLife); 

4.2.5 GreaterThanOrEqual

The greaterThanOrEqual helper takes two arguments produced by Value helpers. Allows only String, HashString, Enumeration, AutoNumber, Integer, Long Decimal, DateTime attributes and their corresponding literals.

The following code sample shows how to use greaterThanOrEqual helper:

const attr = attribute(this.props.myAttributeA.id);
const meaningOfLife = literal(new BigJS(42));

// filter keeps items where value is greater than or equals 42
const filterCondition = greaterThanOrEqual(attr, meaningOfLife); 

4.2.6 LessThanOrEqual

The lessThanOrEqual helper takes two arguments produced by Value helpers. Allows only String, HashString, Enumeration, AutoNumber, Integer, Long Decimal, DateTime attributes and their corresponding literals.

The following code sample shows how to use lessThanOrEqual helper:

const attr = attribute(this.props.myAttributeA.id);
const meaningOfLife = literal(new BigJS(42));

// filter keeps items where value is less than or equals 42
const filterCondition = lessThanOrEqual(attr, meaningOfLife); 

4.3 String Conditions

4.3.1 Contains

The contains helper takes two arguments produced by Value helpers. Allows only String, Integer, Long, Decimal attributes and String literals.

The following code sample shows how to use contains helper:

const attrStr = attribute(this.props.myAttributeA.id); // string attribute
const subStr = literal("secret");

// filter keeps items where value has a substring "secret"
// like "my secret password", "secret file", "top secret"
const filterCondition1 = contains(attrStr, subStr);

// also works with numeric attributes
const attrNum = attribute(this.props.myAttributeB.id); // integer attribute
const subNum = literal("1337");

// filter keeps items where value has sequence of numbers "1337"
// like "133700", "1231337", "913379"
const filterCondition2 = contains(attrNum, substrNum);

4.3.2 StartsWith

The startsWith helper takes two arguments produced by Value helpers. Allows only String, Integer, Long, Decimal attributes and String literals.

The following code sample shows how to use startsWith helper:

const attrStr = attribute(this.props.myAttributeA.id); // string attribute
const subStr = literal("secret");

// filter keeps items where value starts with substring "secret"
// like "secret file", but not "my secret password" or "top secret"
const filterCondition1 = startsWith(attrStr, subStr);

// also works with numeric attributes
const attrNum = attribute(this.props.myAttributeB.id); // integer attribute
const subNum = literal("1337");

// filter keeps items where value stats with sequence of numbers "1337"
// like "133700", but not "1231337" or "913379"
const filterCondition2 = startsWith(attrNum, substrNum);

4.3.3 EndsWith

The endsWith helper takes two arguments produced by Value helpers. Allows only String, Integer, Long, Decimal attributes and String literals.

The following code sample shows how to use endsWith helper:

const attrStr = attribute(this.props.myAttributeA.id); // string attribute
const subStr = literal("secret");

// filter keeps items where value ends with substring "secret"
// like "top secret", but not "my secret password" or "secret file"
const filterCondition1 = startsWith(attrStr, subStr);

// also works with numeric attributes
const attrNum = attribute(this.props.myAttributeB.id); // integer attribute
const subNum = literal("1337");

// filter keeps items where value ends with sequence of numbers "1337"
// like "1231337", but not "133700" or "913379"
const filterCondition2 = startsWith(attrNum, substrNum);

4.4 Logic Conditions

4.4.1 And

The and helper is used to combine other conditions in logical and operation. Takes 2 or more arguments.

The following usage example specifies that all conditions have to be true for an object in order to appear in the resulting filtered set:

const filterCondition = and(
    startsWith(attribute(this.props.myAttributeA.id), literal("Hi")), // myAttributeA starts with string "Hi"
    equals(attribute(this.props.myAttributeB.id), literal(5)), // myAttributeB equals 5
    greaterThan(attribute(this.props.myAttributeC.id), literal(new Date())) // myAttributeC greaterThan current date and time
);

4.4.2 Or

The or helper is used to combine other conditions in logical or operation. Takes 2 or more arguments.

The following usage example specifies that at least one condition have to be true for an object in order to appear in the resulting filtered set:

const filterCondition = or(
    endsWith(attribute(this.props.myAttributeA.id), literal("Z")), // myAttributeA ends with string "Z"
    graterThan(attribute(this.props.myAttributeB.id), literal(10)), // myAttributeB greater that 10
    equals(attribute(this.props.myAttributeC.id), literal(true)) // myAttributeC equals True
);

4.4.3 Not

The not helper inverts a condition. It takes one argument.

The following usage example specifies that myAttributeA have to start with any letter except "X" by inverting startsWith condition:

const filterCondition = not(
    startsWith(attribute(this.props.myAttributeA.id), literal("X")),
);