mydomain
No ADS
No ADS

FlutterArtist FilterModelStructure ex1

  1. Structure of the example
  2. registerFilterModelStructure
  3. Employee59aFilterModel
  4. Employee59aFilterCriteria
In this article, we will define a sample criteria structure where some criteria have parent-child relationships. From this template, the system automatically generates related TildeCriterion(s).
Specifically, company and department are two Criterion(s) with a parent-child relationship defined in the Template. Upon implementation, they generate two independent pairs: company~1/department~1 and company~2/department~2.
Independent mechanism:
  • If you change the selection on InputField("company~1"), it will cause InputField("department~1") to change its data (reload) without affecting InputField("department~2").
In this article, we'll look at how to define a ModelFilterStructure. The result will be a Field-Based JSON like this:
Field-Based JSON
{
    "connector": "AND",
    "conditions": [
        {
            "field": "searchText",
            "operator": "containsIgnoreCase",
            "value": null
        },
        {
            "connector": "OR",
            "conditions": [
                {
                    "field": "departmentId",
                    "operator": "equalTo",
                    "value": 4
                },
                {
                    "field": "departmentId",
                    "operator": "equalTo",
                    "value": 3
                }
            ]
        }
    ]
}
Look at the FilterPanel.
  • "company~1" and "department~1" are two parent-child TildeCriterion(s).
  • Similarly, "company~2" and "department~2" are two parent-child TildeCriterion(s) and are independent of the "company~1/department~1" pair.
If you're new to FlutterArtist Filters, you should read the introduction to FilterModel to understand the overall design concept.
And let's start with the simplest Filter example:
  • Download FlutterArtist Demo

1. Structure of the example

2. registerFilterModelStructure

First, let's look at the complete code of the defineFilterModelStructure() method, then we will analyze each part of its content.
defineFilterModelStructure()
@override
FilterModelStructure defineFilterModelStructure() {
  return FilterModelStructure(
    criteriaStructure: FilterCriteriaStructure(
      simpleCriterionDefs: [
        SimpleFilterCriterionDef<String>(criterionBaseName: "searchText"),
      ],
      multiOptCriterionDefs: [
        // Multi Options Single Selection Criterion.
        MultiOptFilterCriterionDef<CompanyInfo>.singleSelection(
          criterionBaseName: "company",
          fieldName: 'companyId',
          toFieldValue: (CompanyInfo? rawValue) {
            return SimpleVal.ofInt(rawValue?.id);
          },
          children: [
            // Multi Options Single Selection Criterion.
            MultiOptFilterCriterionDef<DepartmentInfo>.singleSelection(
              criterionBaseName: "department",
              fieldName: 'departmentId',
              toFieldValue: (DepartmentInfo? rawValue) {
                return SimpleVal.ofInt(rawValue?.id);
              },
            ),
          ],
        ),
      ],
    ),
    conditionStructure: FilterConditionStructure(
      connector: FilterConnector.and,
      conditionDefs: [
        FilterConditionDef.simple(
          tildeCriterionName: "searchText~",
          operator: FilterOperator.containsIgnoreCase,
        ),
        FilterConditionDef.group(
          groupName: 'G2',
          connector: FilterConnector.or,
          conditionDefs: [
            FilterConditionDef.simple(
              tildeCriterionName: "department~1",
              operator: FilterOperator.equalTo,
            ),
            FilterConditionDef.simple(
              tildeCriterionName: "department~2",
              operator: FilterOperator.equalTo,
            ),
          ],
        ),
      ],
    ),
  );
}
FilterModelStructure
simpleCriterionDefs
Define simple criteria and their data types. If the data types are not simple types, you need to provide a conversion function.
multiOptCriterionDefs
Định nghĩa các tiêu chí có nhiều lựa chọn, kiểu dữ liệu và mối quan hệ của chúng. Nếu kiểu dữ liệu không phải là kiểu đơn giản bạn cần cung cấp một hàm chuyển đổi.
MultiOptFilterCriterionDef
MultiOptFilterCriterionDef<CompanyInfo>.singleSelection(
  criterionBaseName: "company",
  fieldName: 'companyId',
  toFieldValue: (CompanyInfo? rawValue) {
    return SimpleVal.ofInt(rawValue?.id);
  },
  ...
),
No ADS
In the Debug Filter Model Viewer, you will see the criteria and their relationships like this:
FilterConditionStructure
Let's look at the code that defines the conditional structure.
FilterConditionStructure (*)
FilterConditionStructure(
  connector: FilterConnector.and,
  conditionDefs: [
    FilterConditionDef.simple(
      tildeCriterionName: "searchText~",
      operator: FilterOperator.containsIgnoreCase,
    ),
    FilterConditionDef.group(
      groupName: 'G2',
      connector: FilterConnector.or,
      conditionDefs: [
        FilterConditionDef.simple(
          tildeCriterionName: "department~1",
          operator: FilterOperator.equalTo,
        ),
        FilterConditionDef.simple(
          tildeCriterionName: "department~2",
          operator: FilterOperator.equalTo,
        ),
      ],
    ),
  ],
)
Note that although company~1 and company~2 do not directly appear in the condition structure (because the server only needs to filter by departmentId), they are still automatically generated to facilitate data filtering on the interface.
See another similar example where "department~1" and "department~2" are children of "company~".
No ADS
On the FilterPanel, you need UI Components with the same names as the TildeFilterCriterion(s). When the user enters or selects a value on the interface (FilterPanel), it will be updated in the FilterModel.
The current values can be observed in the Debug Filter Model Viewer.

3. Employee59aFilterModel

Since both company~1 and company~2 are generated from the company template, their loading logic is identical. The "magic" lies in the parentMultiOptTildeCriterionValue parameter.
performLoadMultiOptTildeCriterionXData()
@override
Future<XData?> performLoadMultiOptTildeCriterionXData({
  required String multiOptTildeCriterionName,
  required String multiOptCriterionBaseName,
  required Object? parentMultiOptTildeCriterionValue,
  required SelectionType selectionType,
  required EmptyFilterInput? filterInput,
}) async {
  if (multiOptCriterionBaseName == "company") {
    // ApiResult<PageData<CompanyInfo>>
    ApiResult<CompanyInfoPage> result = await _companyProvider.queryAll();
    // IMPORTANT: Throw ApiError.
    result.throwIfError();
    // IMPORTANT: Generics should be declared explicitly.
    var companyListXData = ListXData<int, CompanyInfo>.fromPageData(
      pageData: result.data,
      getItemId: (item) => item.id,
    );
    return companyListXData;
  } else if (multiOptCriterionBaseName == "department") {
    //
    // Because "department" is child of "company".
    // So [parentMultiOptTildeCriterionValue] value is definitely non-null,
    // this guaranteed by the library.
    //
    CompanyInfo companyInfo =
        parentMultiOptTildeCriterionValue as CompanyInfo;
    // ApiResult<PageData<DepartmentInfo>>
    ApiResult<DepartmentInfoPage> result = await _departmentProvider
        .queryAllByCompanyId(companyId: companyInfo.id);
    // IMPORTANT: Throw ApiError if API Error.
    result.throwIfError();
    // IMPORTANT: Generics should be declared explicitly.
    return ListXData<int, DepartmentInfo>.fromPageData(
      pageData: result.data,
      getItemId: (item) => item.id,
    );
  }
  return null;
}

4. Employee59aFilterCriteria

In case you want to send the fieldBasedJSON to the server for querying and filtering data, you simply need to implement a basic FilterCriteria class.
employee59a_filter_criteria.dart
class Employee59aFilterCriteria extends FilterCriteria {
  Employee59aFilterCriteria(); 

  ..
}
Next, implement the abstract method createNewFilterCriteria() within the FilterModel. This method initializes a new FilterCriteria object, and the library automatically assigns the value from the FilterModel's fieldBasedJSON property to the corresponding property in FilterCriteria. (Through the Library-private setters mechanism).
Employee59aFilterModel.createNewFilterCriteria()
@override
Employee59aFilterCriteria createNewFilterCriteria({
  required Map<String, dynamic> tildeCriteriaMap,
}) {
  return Employee59aFilterCriteria();
}
The FilterCriteria object created above is passed as a parameter to the Block.performQuery() and Scalar.performQuery() methods.
Employee59aBlock.performQuery()
@override
Future<ApiResult<PageData<EmployeeInfo>?>> performQuery({
  required Object? parentBlockCurrentItem,
  required Employee59aFilterCriteria filterCriteria,
  required SortableCriteria sortableCriteria,
  required Pageable pageable,
}) async {
  return await employeeRestProvider.queryWithFieldBasedJSON(
    pageable: pageable,
    fieldBasedJSON: filterCriteria.fieldBasedJSON,
  );
}
Finally, utilizing the fieldBasedJSON in the actual API call:
Future<ApiResult<EmployeeInfoPage>> queryWithFieldBasedJSON({
  required Pageable pageable,
  required String fieldBasedJSON,
}) async {
  Map<String, dynamic>? queryParameters = {
    "currentPage": pageable.page,
    "pageSize": pageable.pageSize,
    "fieldBasedJSON": fieldBasedJSON,
  };

  // /rest/advancedGet/employeeInfoPage
  ApiResult<EmployeeInfoPage> result = await flutterArtistDio.jsonGet(
    "/rest/advancedGet/employeeInfoPage",
    queryParameters: queryParameters,
    converter: EmployeeInfoPage.fromJson,
  );
  return result;
}
  • FlutterArtist Debug Filter Criteria Viewer
No ADS
No ADS