Our Syncfusion Angular Query Builder is a graphical user interface component used to build queries. It supports data binding, templates, and importing and exporting queries from and to JSON and SQL formats. Query Builder can be used to generate predicates that are used as conditions in DataManager. It can also auto-populate a data source and map it to appropriate fields from an array of JavaScript objects. In this blog, I am going to walk you through the templating feature, which is used to customize the UI of the Angular Query Builder.
Template
To provide flexibility in customization and a better user experience, the Angular Query Builder offers templates support. A template can include a mixture of static HTML and web controls. The following UI customization options are available:
- Value template: To customize the value column of a rule.
- Rule template: To customize the entire rule.
Value template
Users can customize the user interface to display the value column of a rule (condition) using the template property.
In the following sample code, the:
- Dropdown List component is used to render the custom transaction type field values.
- Checkbox component is used to render the custom payment mode field values.
app.component.html code
<ejs-querybuilder id="querybuilder" #querybuilder [rule] = "importRules"> <e-columns> <e-column field="Category" label="Category" type="string"></e-column> <e-column field="PaymentMode" label="Payment Mode" type="string" [operators]="customOperators" [template]="paymentTemplate"> </e-column> <e-column field="TransactionType" label="Transaction Type" type="string" [operators]="customOperators" [template]="transactionTemplate"> </e-column> <e-column field="Description" label="Description" type="string"></e-column> <e-column field="Date" label="Date" type="string"></e-column> <e-column field="Amount" label="Amount" type="string"></e-column> </e-columns> </ejs-querybuilder> <ng-template #paymentTemplate let-data> <ejs-dropdownlist [dataSource]='paymentDS' [value]='data.rule.value' (change)="paymentChange($event, data.ruleID)"> </ejs-dropdownlist> </ng-template> <ng-template #transactionTemplate let-data> <ejs-checkbox label='Is Expense' [checked]='data.rule.value === "Expense" ? true: false' (change)="transactionChange($event, data.ruleID)"> </ejs-checkbox> </ng-template>
app.component.ts code
import { Component, ViewChild, OnInit } from '@angular/core'; import { QueryBuilderComponent } from './../index'; import { ActionEventArgs, RuleModel } from '@syncfusion/ej2-querybuilder'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { @ViewChild('querybuilder') qryBldrObj: QueryBuilderComponent; public paymentDS: string[] = ['Cash', 'Debit Card', 'Credit Card', 'Net Banking']; public importRules: RuleModel; public customOperators: any; ngOnInit(): void { this.importRules = { 'condition': 'and', 'rules': [{ 'label': 'Transaction Type', 'field': 'TransactionType', 'type': 'string', 'operator': 'equal', 'value': 'Expense' }, { 'label': 'Payment Mode', 'field': 'PaymentMode', 'type': 'string', 'operator': 'equal', 'value': 'Cash' }] }; this.customOperators = [ {value: 'equal', key: 'Equal'}, {value: 'notequal', key: 'Not Equal'} ]; } transactionChange(e: any, ruleID: string): void { let elem: HTMLElement; elem = document.getElementById(ruleID).querySelector('.e-rule-value'); this.qryBldrObj.notifyChange( e.checked === true ? 'Expense' : 'Income', elem, 'value'); } paymentChange(e: any, ruleID: string): void { let elem: HTMLElement; elem = document.getElementById(ruleID).querySelector('.e-rule-value'); this.qryBldrObj.notifyChange(e.value as string, elem, 'value'); } }
The following .gif image shows the UI customizations done in the Angular Query Builder using the Value Template property.
Rule template
Users can also customize the user interface to display the entire rule using the ruleTemplate property.
Customize the Age field using the ruleTemplate property as shown in the following sample code. Here, set the default condition as greater than or equal and render the slider component for the value column. Finally, show the result using a table in the rule UI template.
app.component.html code
<ejs-querybuilder id="querybuilder" #querybuilder width="100%" [rule] = "importRules" (actionBegin)="actionBegin($event)"> <e-columns> <e-column field="EmployeeID" label="Employee ID" type="number"></e-column> <e-column field="FirstName" label="First Name" type="string"></e-column> <e-column field="LastName" label="LastName" type="string"></e-column> <e-column field="Age" label="Age" type="number" [ruleTemplate]="ruleTemplate"></e-column> <e-column field="City" label="City" type="string"></e-column> <e-column field="Country" label="Country" type="string"></e-column> </e-columns> </ejs-querybuilder>
Template code
<ng-template #ruleTemplate let-data> <div class="e-rule e-rule-template"> <div class="e-rule-header"> <div class="e-rule-filter"> <ejs-dropdownlist (change)="fieldChange($event)" [fields]="data.fields" [dataSource]="data.columns" [value]="data.rule.field"></ejs-dropdownlist> </div> <div *ngIf="data.rule.type ==='number'" class="e-rule-value e-slide-val"> <ejs-slider [value]='data.rule.value' [ticks]='rangeticks' min=30 max=50 id="{{data.ruleID}}_valuekey0" (change)="valueChange($event, data.ruleID)"></ejs-slider> </div> <div class="e-rule-btn"> <button id="{{data.ruleID}}_option" (click)="viewDetails(data.ruleID)" class="e-primary e-btn e-small"> <span class='e-content'>View Details</span> </button> <button class="e-removerule e-rule-delete e-btn e-small e-round"> <span class="e-btn-icon e-icons e-delete-icon"></span> </button> </div> </div> <div id="{{data.ruleID}}_section" class="e-rule-content e-hide"> <table id="{{data.ruleID}}_datatable" class='e-rule-table e-hide'> <thead> <tr><th>EmployeeID</th><th>FirstName</th><th>Age</th></tr> </thead> <tbody></tbody> </table> </div> </div> </ng-template>
Customized CSS code
ejs-querybuilder { display: block; } .e-rule-template { padding-bottom: 12px; } .e-query-builder .e-slider-container.e-horizontal { padding: 0px 0 0 18px; height: 0; } .e-query-builder .e-slider-container.e-horizontal .e-slider { top: calc(50% - 7px); } .e-query-builder .e-slide-val { width: 35%; } .e-rule-btn { float: right; padding-top: 12px; } .e-query-builder .e-hide { display: none; } .e-rule-content { margin: 0px 0 0 13px; height: 180px; width: 335px; overflow-y: auto; border: 1px solid #e0e0e0; } .e-rule-table { border-collapse: collapse; } .e-rule-table th, .e-rule-table td { border: solid #e0e0e0; border-width: 1px 0 0; padding: 8px 21px; }
app.component.ts code
export class AppComponent implements OnInit { @ViewChild('querybuilder') qryBldrObj: QueryBuilderComponent; public actionArgs: ActionEventArgs; public importRules: RuleModel; public rangeticks: Object; ngOnInit(): void { this.importRules = { 'condition': 'and', 'rules': [{ 'label': 'Age', 'field': 'Age', 'type': 'number', 'operator': 'greaterthanorequal', 'value': 32 }] }; this.rangeticks = { placement: 'Before', largeStep: 5, smallStep: 1, showSmallTicks: true }; } actionBegin(args: ActionEventArgs): void { if (args.requestType === 'template-initialize') { this.actionArgs = args; args.rule.operator = 'greaterthanorequal'; if (args.rule.value == '') { args.rule.value = 32; } } } fieldChange(e: any): void { this.qryBldrObj.notifyChange(e.value, e.element, 'field'); }; valueChange(e: any, ruleID: string): void { let elem: HTMLElement = document.getElementById(ruleID); this.qryBldrObj.notifyChange(e.value as Date, elem, 'value'); this.refreshTable(this.qryBldrObj.getRule(elem), ruleID); } viewDetails(ruleID: string): void { let ruleElem: HTMLElement = document.getElementById(ruleID); let element: HTMLElement = document.getElementById(ruleID + '_section'); if (element.className.indexOf('e-hide') > -1) { this.refreshTable(this.qryBldrObj.getRule(ruleElem), ruleID); element.className = element.className.replace('e-hide', ''); document.getElementById(ruleID + '_option').querySelector('.e-content').textContent = 'Hide Details'; } else { element.className += ' e-hide'; document.getElementById(ruleID + '_option').querySelector('.e-content').textContent = 'View Details'; } } refreshTable(rule: RuleModel, ruleID: string): void { let template: string = '<tr><td>${EmployeeID}</td><td>${LastName}</td><td>${Age}</td></tr>'; let compiledFunction: any = compile(template); let predicate: Predicate = this.qryBldrObj.getPredicate({condition: 'and', rules: [rule]}); let dataManagerQuery: Query = new Query().select(['EmployeeID', 'LastName', 'Age']).where(predicate); let result: object[] = new DataManager(employeeData).executeLocal(dataManagerQuery); let table: HTMLElement = document.getElementById(ruleID + '_datatable') as HTMLElement; if (table) { if (result.length) { table.style.display = 'block'; } else { table.style.display = 'none'; } table.querySelector('tbody').innerHTML = ''; result.forEach((data) => { table.querySelector('tbody').appendChild(compiledFunction(data)[0].querySelector('tr')); }); } } }
The following .gif image shows all the UI customizations done in the Query Builder using the Rule Template property.
Resources
You can find the projects in the following UG documentation links,
- Angular Query Builder UI Customization using Value Template
- Angular Query Builder UI Customization using Rule Template
Conclusion
I hope you now have a better understanding of the UI customization in the Angular Query Builder control. What else do you expect from our Query Builder? Please share your thoughts in the comments section.
Our Query Builder component is also available for the Blazor, ASP.NET (Core, MVC), JavaScript, React, and Vue platforms.
If you’re already a Syncfusion user, you can download the product setup to try out this control. Otherwise, you can download a free 30-day trial.
If you have any questions about these features, please contact us through our support forum, Direct-Trac, or feedback portal. We are happy to assist you!
If you like this blog post, we think you’ll also like the following articles, too:
Comments (2)
Would be great if we could have a Blazor version of this blog.
Hi Michael,
We have already showcased these template samples in our Blazor user guide. Please refer to the below link,
https://blazor.syncfusion.com/documentation/query-builder/templates/
Regards,
Satheeskumar S
Comments are closed.