Table of Contents

Lookups

Lookups let you define the behavior of the lookup component.
This component can manually be used in UI view templates, but it is also automatically used by Neos when displaying a reference or the scalar property of a reference in a form field.

Default behavior

By default, in a UI view, when no explicit lookup is defined on a reference or its scalar property, an auto-generated lookup is used.

Warning

You get a warning during the generation when this happens.

The auto-generated lookup is based on the properties of one of the UI views of the referenced entity :

  • for a scalar property, the first UI view of the first entity view of the referenced entity
  • for a reference property, the first UI view of the related entity view defined on the entity view property

The key property of the UI view is returned and displayed by the lookup.

Warning

You get a warning during the generation when the referenced entity has no UI view exposing its key property.
If this is the case, when the reference is placed in the template of a UI view, a simple input is displayed instead of a lookup.

Creating a custom lookup

To avoid problems, it is recommended to create a custom lookup instead of using the auto-generated one.
In Neos Studio, you can create a lookup in the tree view by clicking on the + button of the Frontend/Lookups node.
A lookup must have a unique name.

Interface mode

The default "Lookup" mode uses an efficient paginated server-side search (see further below).

It is however possible to switch to an exhaustive mode called "Combobox" mode. In this mode, all the values will be retrieved from the server and displayed in a Combobox.

Warning

Please note that using an exhaustive listing may have a negative impact on performance. The default "Lookup" mode using paginated server-side search is preferred when the displayed property may have a wide variety of possible values.

Search mode

When a user starts typing in the lookup input field, it triggers a request to find matching references depending on the lookup Search mode.

If the search mode is Default, the request looks for items that contain the user input in their property used for search.

If the entity view on which the lookup UI view is based contains at least one property with Quick search enabled, the the lookup can use the Quick search search mode.
In this case, the request looks for items that contain the user input in their quick search properties.

For performance recommendations and generated query examples, see the lookup performance guide.

UI view

UI view on which the lookup is based, it needs to be bound to an entity view.
The entity view is used to get the items displayed by the lookup.

When the lookup modal is opened, depending on the values of Use UI view template, it either displays :

  • the entire UI view
  • an auto-generated datagrid based on the UI view properties, a filter bar and a pagination bar

Use UI view template

Since version 1.14, indicates whether the UI view template is displayed in the lookup modal.

When checked, the full UI view template is displayed.
All UI view features such as adding, modifying and deleting items are available if enabled on the UI view.

Otherwise, displays an auto-generated datagrid based on the properties of the UI view, a filter bar and and a pagination bar.

Data selection

If the template contains a grid, clicking on one row automatically selects the item.
Data selection can also be triggered by code using the ReturnItemAsync(item) method as follows:

  1. In the lookup UI view, create a UI method (e.g. named ChooseItem) with a parameter of the same type as the UI view. The method makes the call to the ReturnItemAsync method of the view model passing the selected item.

Example: ImageLookupUI UI view

UI method:

Name: ChooseItem
Parameters:
  - item (MyNamespace.CSharpAbstractions.ImageLookupUI)
Code: ReturnItemAsync(item);
  1. In the UI view template, a method button and set the method name to ChooseItem.

Example: ImageLookupUI template

Template:

<repeat values="@Datasource">
    <button type="method" method-name="ChooseItem" size="small" variant="ghost" layout:padding="none" tooltip:text="$Item.Name">
        <card>
            <image name="$Item.Name" size="extralarge"></image>
        </card>
    </button>
</repeat>

When the user clicks on the button, $Item is automatically passed to the ChooseItem method.

Execute UI view rules

Since version 1.14, event rules defined on the UI view of the lookup are executed when the lookup modal is opened.

When unchecked, this property disables them when the lookup modal is opened.
Also disables the execution of the Navigating event rule of the UI view that contains the lookup when the lookup modal is opened.

Warning

Must be unchecked to keep the same behavior as before version 1.14.

Creation UI view

Name of the UI view to open when creating a new item from the lookup.

When this property is set and the user inputs a value, a + button is displayed in the input and an Add option is displayed in the suggestion list.
They open the creation UI view to allow the user to create a new item. The input corresponding to the effective property used for search is prefilled.
When saving, the creation UI view is automatically closed and the lookup value becomes valid. Closing it without saving cancels the creation.

Note

To be selectable as a creation UI view, a UI view has to:

  • be based on the same entity as the lookup UI view
  • allow update
  • be in mono record mode

Creation open mode

Determines the mode in which the creation UI view is opened. By default, opens it in an auto-size popup.

Editing UI view

Name of the UI view to open when editing the selected item of the lookup.

When this property is set and the lookup has a valid value, an edit button is displayed in the input.
It opens the editing UI view to allow the user to edit the selected item.

When saving, the lookup value is updated.

Note

To be selectable as a editing UI view, a UI view has to:

  • be based on the same entity as the lookup UI view
  • be in mono record mode

Persisted property for a scalar property

In case the lookup is used to feed a scalar property, it should know which property of the referenced UI view it will persist.
This is the role of the value property set on the lookup configuration.

Note

If the lookup is used on a reference property, the value property will not be used for persistence, the persisted value being the complete reference value.

Displayed property

The displayed property defines the value shown to the user in the input and in suggestions.

When it is not set, the lookup displays the Persisted property for a scalar property.

When Property used for search is not configured and the lookup search mode is Default, the displayed property also becomes the effective property used to search and sort suggestions.

The search property defines which property is used to search and sort suggestions when the lookup search mode is Default.

In most cases, this is the property on which the user input is matched.

When it is not set, the lookup uses the displayed property. If no displayed property is configured, it uses the persisted value property.

When it is different from the displayed property, users search on one property but still see another in the suggestions. This is useful when you want to display a readable label but search on a richer field, such as a dedicated SearchText.

The advanced lookup dialog does not automatically use this property for its default free-text search. That behavior depends on the quick-search configuration of the opened UI view.

In Quick search mode, SearchProperty is ignored. The lookup uses the quick search properties of the entity view instead.

Note

If the effective search property has a character casing, it will also be applied when searching the list of values. This only applies to lower and upper casing. To find out more about character casing, see the documentation on properties.

For performance recommendations and generated query examples, see the lookup performance guide.

Include total count in suggestions

This option is useful only in paginated lookup mode.

When enabled, the dropdown asks the server for the total number of matches. If more matches exist than the displayed suggestions, the footer shows a message such as 10 displayed / 37 found.

When disabled, the dropdown does not ask for the total number of matches. If more matches exist than the displayed suggestions, the footer only shows a limit hint such as Only the first 10 results are shown.

Properties displayed in suggestions

When typing in the lookup, a list of suggestions to choose from is displayed.
By default, only the displayed property is shown.

If you want to display more properties, you can add them here. These properties can be selected from properties of the UI view of the lookup.
They are displayed in the suggestion list as a table with a column for each property.

Note

If you only set one property, it replaces the default displayed property in the suggestion list.
If you set several properties, a header is displayed at the top of the grid to identify the different columns. The caption of each column is the UI view property caption.

Allow free value

This property allows the user to input any value in the lookup, even if it does not match any of its items.
The value is accepted as is without validation error.

Warning

Only lookups bound to a scalar property can use this option. A lookup bound to a reference can only be updated with a fully qualified object.

Using a custom lookup

Specifying the lookup on a UI view property

After creating a lookup, you have to specify it in the Lookup field of the UI view property that will display it.

Warning

On a reference, the UI view of the lookup needs to be related to the same entity as the one the reference is related to.

Specifying the lookup on field / computed

It is also possible to set a lookup without being bound to a UI view property. For example, this can be used to feed a field / computed of the UI view with predetermined values.

This is done by declaring the following in the UI template :

<lookup label="Car model" value="@Fields.CarModel" name="CarModels" />

In this case, the lookup returns its Persisted property for a scalar property. This means field CarModel will receive the name of the car model, therefore its type needs to be string.

If you want the lookup return a whole CarModel item with all of its properties, you need to set the is-reference attribute as follows :

<lookup label="Car model" value="@Fields.CarModel" name="CarModels" is-reference="true" />

In this case, CarModel will receive an object of type MyCluster.CSharpAbstractions.CarModelUI, therefore its .NET data type needs to be `MyCluster.CSharpAbstractions.CarModelUI.

Getting and setting values of a property with lookup by code

In UI code, several C# methods allow you to get of set the values of a property with lookup.

Getting the persisted value

To get the persisted value of a property with lookup, simply access the property on the model.

Example

int categoryId = Item.CategoryID;

Getting the displayed value

When a property has a lookup with a display property, you can get the value displayed in the lookup instead of the persisted value.

Methods

T? GetLookupDisplayValueAsync<T>(string propertyName)
T? Get{PropertyName}DisplayValueAsync()

Examples

string? displayValue = await Item.GetLookupDisplayValueAsync<string>(nameof(Item.CategoryID));
string? displayValue = await Item.GetCategoryIDDisplayValueAsync();

Setting the persisted value

Without control

If you do not want the lookup to check whether the value you set corresponds to one of the items listed by the lookup, you can simply set the property on the model.

Example

Item.CategoryID = 1;

With control

Methods

bool TrySetLookupValueAsync<T>(string propertyName, T value)
bool TrySet{PropertyName}Async(T value)

These methods try to set the value of a property with lookup.

They return a boolean indicating whether the value corresponds to one of the items listed by the lookup.
If not, the value of the property is not changed.

When the item belongs to a UI view, these methods execute the ReferenceRetrieving event rule for the lookup property before validating the value. The rule receives ReferenceRetrievingTrigger.TrySet and can add filters, sorts, or entity view parameters to the lookup request.

Examples

bool isValid = await Item.TrySetLookupValueAsync(nameof(Item.CategoryID), 1);
bool isValid = await Item.TrySetCategoryIDAsync(1);

Setting the persisted value from a display value

When a property has a lookup with a display property, it is possible to set the persisted value by deducing it from a display value.

Methods

bool TrySetLookupValueFromDisplayValueAsync<T>(string propertyName, T displayValue)
bool TrySet{PropertyName}FromDisplayValueAsync(T displayValue)

These methods try to set the value of a property with lookup from a display value.
The behavior is similar to a user typing a value in the lookup.

They return a boolean indicating whether the display value corresponds to one of the items listed by the lookup.
If not, the value of the property is set with a temporary invalid value and the invalid display value is displayed in the lookup.
If the lookup has a creation UI view, the + button is displayed to allow the user to create the item.
When saving with an invalid display value, the lookup displays a validation error.

When the item belongs to a UI view, these methods execute the ReferenceRetrieving event rule for the lookup property with ReferenceRetrievingTrigger.TrySet.

Examples

bool isValid = await Item.TrySetLookupValueFromDisplayValueAsync(nameof(Item.CategoryID), "ONE");
bool isValid = await Item.TrySetCategoryIDFromDisplayValueAsync("ONE");

Setting a property to bind to the free value

With a lookup bound to a scalar property, you can allow free value. But you can't get the free value entered by the user.

Take the following example :

classDiagram
    class Order {
        int Id
        int? CustomerId
        string? CustomerName
    }
classDiagram
    class Customer {
        int Id
        string Name
    }

A lookup is defined on Order.CustomerId property with display property Name which allows free value.

When a free value is entered by the user, the free value should be set in Order.CustomerName.

On the UI view property Order.CustomerId, specifies the Lookup free value property name to CustomerName. Neos will automatically set CustomerName:

  • to the free value when the value does'nt corresponding to a Customer reference.
  • to null when the selected value is from a reference.

The Lookup free value property name is only allowed if the lookup of the UI view property has the value Allow free value set to true.

Example in technical demos

You can find an example in TechnicalDemos cluster in the menu Lookups > Lookup free value > Orders. All metadata in the Lookups module are prefixed with Lookups.

In this example:

  • if a customer is from an existing, then the LookupsOrder.CustomerId property is set.
  • if the entered text is not associated to and existing then, it's a free value. So, the LookupsOrder.CustomerName is set.

Special case

Even if the customer is comes from an existing (LookupsOrderUI.CustomerId property is set), you want to set also the LookupsOrderUI.CustomerName.

Warning

First of alls, before processing data on the front end, ask yourself if the process should be done on the back side. In this case, if the customer change is made via API, CustomerName will not be updated.

To process front-end data only, create a property changed event rule on CustomerId property, and add the following code :

LookupsCustomerUI? customer = Arguments.SelectedLookupItem as LookupsCustomerUI;
if(customer != null)
{
    Item.SuspendChangeTracking(); // Event tracking must be suspended, otherwise changing the property value associated with the free value will reset `LookupsOrderUI.CustomerId` to null.
    Item.CustomerName = customer.Name;
    Item.ResumeChangeTracking();
}

Setting the displayed value of the lookup of an unbound property

A lookup with a persisted value different from the displayed value may not display the expected value in the following cases:

  • The source property on the entity view is unbound.
  • The entire entity view is unbound.

In such cases, it is the developer's responsibility to manually provide the displayed value.

Client side

The displayed value of the lookup of an unbound property can be set on the client side.

To do so, you need to specify the name of another property which will act as the displayed value in the lookup.

For example, in the Initialized UI view event rule, you can set property CategoryName as the displayed value of the lookup of property CategoryID by doing the following:

Properties.CategoryID.LookupDisplayProperty = Properties.CategoryName;

Server side

Warning

Since version 2.5, we recommend setting the displayed value on unbound properties on the client side because it is simpler and easier to understand.

Displayed values are transmitted as additional data within the elements returned by the server to populate the lookup.

To initialize the displayed value, use the IAdditionalDataInitializer service. Examples of its usage can be found in the LookupDisplayProperty module within TechnicalDemos.

The following example demonstrates how to set the displayed value for an unbound CategoryID property in a bound entity view IProductView:

public class Retrieved : IRetrievedRule<IProductView>
{
    private readonly IAdditionalDataInitializer<IProductView> _additionalDataInitializer;

    public Retrieved(IAdditionalDataInitializer<IProductView> additionalDataInitializer)
    {
        _additionalDataInitializer = additionalDataInitializer;
    }

    public Task OnRetrievedAsync(IRetrievedRuleArguments<IProductView> args, CancellationToken cancellationToken)
    {
        // Retrieve the displayed values for categories
        Dictionary<int, string> categoryNames = ...;

        foreach (IProductView item in args.Items.Where(i => i.CategoryID.HasValue))
        {
            // Attempt to assign displayed values
            _additionalDataInitializer.TrySetDisplayValue(item, nameof(item.CategoryID), categoryNames[item.CategoryID!.Value]);
        }

        return Task.CompletedTask;
    }
}

For an unbound entity view, the same principle applies, but the code should be placed in the Retrieving event instead of Retrieved.

Handling property names in TrySetDisplayValue

The property name expected by TrySetDisplayValue can be either:

  • A simple name (e.g., MyProperty)
  • A reference-associated name (e.g., MyReference.MyProperty)

If you are unsure of the correct value to pass, you can check the implementation of TrySetDisplayValue by looking for the {MyUIView}AdditionalData class in the generated solution.

TrySetDisplayValue vs. SetDisplayValue

There is also a SetDisplayValue method, but it throws an exception if the assignment fails. It is recommended to use TrySetDisplayValue instead, as SetDisplayValue will systematically fail when the entity view is not invoked from a UI view. This would make your entity view unusable for other server-side processes or direct API calls.

How to ?

How to deal manually with a creation UI view ?

When a lookup has a creation UI view, the user can create a new item from the lookup. When saving the new item, the lookup value is automatically updated with the persisted value of the new item.

In some cases, you may want to deal manually with the creation UI view. For example, you may want to have an intermediate UI view before access to the creation form of the item. You need to deal manually with lowering the item in the lookup. To do so, you can use the callback function with the new item as parameter :

Closing event of the creation UI view :

Arguments.Result = new NavigationResult(NavigationResultState.Ok, DatasourceCurrent);

Navigating event of the UI view containing the lookup:

Arguments.Options.WithCallback<LookupsPartyUI>(r =>
    {
        if (r.Value != null)
        {
            DatasourceCurrent.TrySetPartyIdAsync(r.Value.Id, r.Value.Name);
        }
    });

To see more about callback function, see the Navigation documentation.

If you want to get the value the user entered in the lookup, the framework automatically pass the information in the navigation to the creation UI. The information can be retrieved as follows :

string invalidValueEnteredByUser = (string)ViewContext["neos-lookup-input"];