Developer Docs

SpringBot Customising the Dropdown Component Contents

Model

Fishnatics Model

For this article we will be using our Fishnatics entity model as our example, specifically the Tank entity.

From this example, we can see the two model-driven methods for populating our dropdown component.

Enumerated Types

These are controlled by the Enum attribute type.

These would be selected for the use case where the choices do not change often.

Enumerated types example

References

These are controlled by the references between entities.

These would be selected when the choices change or a binding between entities needs to be made.

Reference example

Customisation

There are many different use cases for customising the content of a dropdown.

We will go through a couple of the primary ones in this article.

Filtering on a Reference

Sometimes, you want to only show references in a dropdown which match a condition.

For example, we may only want to be able to add fish that are Alive to a Tank.

To achieve this we will filter on the on the reference with our Fish entity and select for those that have alive=true.

Filter on reference

Abstract Model

We will first look at our abstract model found at clientside/src/app/lib/models/abstract.model.ts

export interface ModelRelation {
    // Name of the Relation to bind with, usually is the id
    name: string;
    // Label to display in the input component, usually the Name or OppositeName of the references
    label: string;
    // The field of the model to display in the component
    displayName: string;
    type: ModelRelationType;
    // Validators to be used in the form
    validators?: ValidatorFn[];
    // Function for the dropdown to apply the server side searching
    searchFunction?: Subject<string>;
    // Observable to do the server side fetching
    collection?: Observable<any[]>;
    // Collection state of one instance of relations. Used in searching
    stateConfig?: PassableStateConfig<any>;
    group?: { id: string, displayName: string };
    // Whether the data is sensitive, which could not be fetched from db, and would be show as password fields
    isSensitive?: boolean;
    // Used to control whether to hide element by from the view
    hideElement?: boolean;
    index?: number;
    [s: string]: any;
    // % protected region % [Add any additional model relation fields here] off begin
    // % protected region % [Add any additional model relation fields here] end
}

For this task, we will be using the searchFunction property.

Given that this makes use of the server-side search, we first must make sure that our alive attribute on our Fish entity is searchable.

Make searchable

Make sure you rebuild your app once you have made this change.

Looking at the FishService.java found at serverside/src/main/java/com/springbot/fishnatics/services/FishService.java, we can see where this change has occurred in the processCondition(Where condition) method. You will notice there is now a condition for our alive attribute.

Adding the Reference Search Criteria

  1. First we we need to set how the predicate for alive is defined.

    Turn on the protected region called Add any additional logic after the query parameters of entity properties here and add the following:

     // % protected region % [Add any additional logic after the query parameters of entity properties here] on begin
      switch (condition.getOperation()) {
          case equal:
              predicate = entity.alive.eq(Boolean.valueOf(condition.getValue()));
              break;
          case notEqual:
              predicate = entity.alive.ne(Boolean.valueOf(condition.getValue()));
              break;
          default:
              // Allow to fall through
      }
      // % protected region % [Add any additional logic after the query parameters of entity properties here] end
    
  2. Now, we need to override the default reference collection handling within the CRUD tile. Open clientside/src/app/admin/tiles/crud/tank/tank.admin.tile.crud.component.ts and activate the protected region Add any additional class methods here.
  3. Add the following:

     // % protected region % [Add any additional class methods here] on begin
      private customPrepareReferenceCollections() {
          this.modelRelations.fishs.stateConfig = {
              pageIndex: 0,
              pageSize: this.pageSize,
              collectionId: this.collectionId,
              queryParams: {
                  pageSize: this.pageSize,
                  pageIndex: 0,
                  where: [
                      [
                          {
                              path: "alive",
                              operation: QueryOperation.EQUAL,
                              value: "true"
                          }
                      ]
                  ]
              }
          } as PassableStateConfig<FishModel>;
        
          this.store.dispatch(new fishModelAction.InitialiseFishCollectionState(this.modelRelations.fishs.stateConfig));
          this.modelRelations.fishs.collection = this.store.select(getFishCollectionModels, this.collectionId);
          this.customAddFishSearchFunction(this.modelRelations.fishs, fishModelAction.FetchFishModelsWithQuery);
        
          this.store.dispatch(new fishModelAction.FetchFishModelsWithQuery(this.modelRelations.fishs.stateConfig));
      }
        
      private customAddFishSearchFunction(modelRelation: ModelRelation, action: new (...args: any[]) => NgRxAction) {
          modelRelation.searchFunction = new Subject<string>();
          modelRelation.searchFunction.pipe(
              debounceTime(500),
              distinctUntilChanged()
          ).subscribe(
              (term: string) => {
                  modelRelation.stateConfig.queryParams = {
                      pageSize: this.pageSize,
                      pageIndex: 0,
                      where: [
                          [
                              {
                                  path: "alive",
                                  operation: QueryOperation.EQUAL,
                                  value: "true"
                              },
                              {
                                  path: modelRelation.displayName,
                                  operation: QueryOperation.CONTAINS,
                                  value: term
                              }
                          ]
                      ]
                  };
                  this.store.dispatch(new action(modelRelation.stateConfig));
              }
          );
      }
      // % protected region % [Add any additional class methods here] end
    

    The key changes we have made are,

    • Added query parameters to use our new filter for both initialise and search
    • Replace our fetch all with a fetch with query so that our filter is applied
  4. To deactivate the original prepareReferenceCollections() method, we need to activate the protected region called Add any additional code here before the main logic of prepareReferenceCollections and add the following:

     // % protected region % [Add any additional code here before the main logic of prepareReferenceCollections] on begin
      return;
      // % protected region % [Add any additional code here before the main logic of prepareReferenceCollections] end
    

    Note: This does turn the old method into dead code, this will be rectified in future versions.

  5. Final step is to execute our new customPrepareReferenceCollections() method. Activate the protected regions called Add additional processing for state configuration here and Add additional processing for Create mode before the main body here and place the following inside both:

     this.customPrepareReferenceCollections();
    

    This will enable our new method to replace the old for both create and update.

What you will now see is that the Fish entity dropdown is will now only show Fish that are alive.


Transformation of Dropdown Data

Perhaps the data does not require filtering but rather transformation, for example, adding internationalisation support.

This can easily be achieved globally for a given attribute by overriding our getter methods.

Following on from the previous example we will transform our Fish Entity.

Sequence diagram for transform
  1. First thing to take note of is our displayName for our fish reference. Open clientside/src/app/models/tank/tank.model.ts and locate the method called getRelations(). You will notice our fishs object. This has a property called displayName which references an attribute on the FishModel (found at fish.model.ts). This is the value we will wish to transform in this example.
  2. Next we want to override our getter method for our display attribute. Open the FishEntity.java file from serverside/src/main/java/com/springbot/basic/entities/FishEntity.java, find the protected region Add any additional class methods here and activate it.
  3. Add the following:

     // % protected region % [Add any additional class methods  here] on begin
      public String getName() {
          return this.name.toUpperCase();
      }
        
      public void setName(String name) {
          this.name = name.toLowerCase();
      }
      // % protected region % [Add any additional class methods  here] end
    

    This will perform the simple text transform of converting the name to upper case every time we fetch it, and converting it back to lowercase every time we save it back.

    Basic transform

There are many ways of achieving a solution for this use case, including:

This article only explores the simple case.

Was this article helpful?

Thanks for your feedback!

If you would like to tell us more, please click on the link below to send us a message with more details.

Tool:

Generate

Iterate

Bot:

C#Bot

SpringBot

On this page

New to Codebots?

We know our software can be complicated, so we are always happy to have a chat if you have any questions.