Implement a cascading effect of the drop-down search box, the results of the implementation as shown in the figure belowThis component is used to learn some subtle logic, such as how to calculate the length of text in the input box; How to get cursor position; How to realize the scroll bar with up and down the keyboard press to move……
Specific requirements are as follows
- A maximum of three levels can be cascaded with “. “as the concatenation character
- The search box follows the “. “in the text box to move backward, the maximum distance to the right cannot exceed the width of the text box
- If the user modifies the previous cascading content, no search is performed and the search box is hidden. If the user enters “. “before, delete all the content after”. “and search the current related content
And then we write our logic based on the requirements
First we set up the HTML page
<input #targetInput autocomplete="off" nz-input [(ngModel)]="searchValue" (keydown)="handlePress($event)" (input)="handleSearchList()"/> <div #searchList class="search-popup" [hidden]="! visible" (keyDown)="onKeydown($event)"> <nz-spin [nzSpinning]="searchLoading" [class.spinning-height]="searchLoading"> <div class="data-box" *ngIf="searchData && searchData.length ! ~ <li id="item" *ngFor="let item of searchData; let i = index;" [class.item-selected]="curIndex === i" (mouseover)='hoverDataItem(i)' (click)="onSelectClick(item)"> <span [innerHTML]="item | highlightSearchResult:searchValue | safe: 'html'"></span> </li> </ul> </div> </nz-spin> </div>Copy the code
.search-popup {
height: 376px;
width: 246px;
overflow-y: auto;
box-shadow: 0 2px 8px rgba(0,0,0,.15);
border-radius: 4px;
position: absolute;
background-color: #fff;
z-index: 999;
top: 92px;
right: 61px;
.data-box {
margin: 0 10px;
&:not(:last-child) {
border-bottom: 1px solid #E4E5E7;
}
.no-search-data {
display: inline-block;
width: 100%;
text-align: center;
color: #C3C9D3;
line-height: 40px;
}
}
& ul {
margin: 0 -10px;
margin-bottom: 0;
text-align: left;
}
& li {
padding: 3px 10px;
position: relative;
list-style: none;
height: 32px;
line-height: 26px;
&:hover {
cursor: pointer;
background-color: #e6f7ff;
}
&.item-selected {
background-color: #E6F7FF;
}
}
&.item-selected {
background-color: #E6F7FF;
}
.hidden-box {
display: inline-block;
border: 1px solid #ddd;
visibility: hidden;
}
Copy the code
Implement the relevant logic
According to the first two requirements, we need to move backward according to the “. “in the text box, the maximum distance to the right can not be greater than the width of the text box.
We need to convert the string in the text box to an array based on “. “and we need to find a way to get the length of the text in the text box.
How do I get the length of the text in the text box? We can put the contents of the text back into a display: inline-block div container and get the width of the container, as shown below
// html <! <div class="hidden-box" #firstLevel></div> <div class="hidden-box" #secondLevel></div> <div class="hidden-box" #allLevel></div> Renderer2 } from '@angular/core'; export class SearchListComponent { @ViewChild('searchList', { static: true }) public searchList: ElementRef; @ViewChild('firstLevel', { static: true }) public firstLevel: ElementRef; @ViewChild('secondLevel', { static: true }) public secondLevel: ElementRef; @ViewChild('allLevel', { static: true }) public allLevel: ElementRef; constructor(private _renderer: Renderer2) {} public setSearchPosition(rightValue: string): void { this._renderer.setStyle( this.searchList.nativeElement, 'right', rightValue); } public setSearchListPosition(targetValue: string): void { const inputWidth = 217; const defaultRightPosition = 60; const maxRightPosition = -148; const firstLevel = this.firstLevel.nativeElement; const secondLevel = this.secondLevel.nativeElement; const allLevel = this.allLevel.nativeElement; const targetValueArr = targetValue ? targetValue.split('.') : []; // Convert the contents of the input to an array according to ". ", and assign the related contents to the new DIV container. firstLevel.innerHTML = targetValueArr && targetValueArr[0]; secondLevel.innerHTML = targetValueArr && targetValueArr.length > 1 ? targetValueArr[1] : ''; // After getting the relevant width, Implement a drop-down box moving the logic of the if (firstLevel. The offsetWidth > = inputWidth | | (firstLevel. OffsetWidth + secondLevel. OffsetWidth) > = inputWidth || allLevel.offsetWidth >= inputWidth) { this.setSearchPosition(this._renderer, this.searchList, `${maxRightPosition}px`); } else if (targetValueArr.length <= 1) { this.setSearchPosition(this.renderer, this.searchList, '61px'); } else if (targetValueArr.length <= 2) { this.setSearchPosition(this.renderer, this.searchList, `${defaultRightPosition - firstLevel.offsetWidth}px`); } else if (targetValueArr.length <= 3) { this.setSearchPosition(renderer, this.searchList, `${defaultRightPosition - firstLevel.offsetWidth - secondLevel.offsetWidth}px`); }}}Copy the code
Here, we can complete the first and the second requirement, we take a look at the third requirement: mainly according to the location of the user input and modify the content, to decide whether to display the search and display a drop-down box, if the user input is not “. “we do not show, if the user input in the previous cascade”. “we need to help users search results.
In order to complete requirement 3, we need to know where the user is operating
Public getCursorPosition(Element: HTMLInputElement): number {let cursorPosition = 0; if (element.selectionStart || element.selectionStart === 0) { cursorPosition = element.selectionStart; } return cursorPosition; } public handlePress(Event: KeyboardEvent): void {this.curpressKey = event.key; } public handleSearchList(value: string): void {this.curIndex = 0; const cursorPosition = this.getCursorPosition(this.targetInput.nativeElement); // get cursor position let targetValue = value; const targetValueArr = targetValue ? targetValue.split('.') : []; const valueArrLength = targetValueArr.length; this.setSearchListPosition(targetValue); / / adjust / / locating those cases and displays a drop-down box to search the if (valueArrLength = = = 1 | | valueArrLength = = = 2 && cursorPosition > = targetValueArr[0].length + 1 || valueArrLength === 3 && cursorPosition >= targetValueArr[0].length + targetValueArr[1].length + 2) { this.searchLoading = true; this.visible = true; . Get data from the dropdown} else {this.hidepopup (); }}Copy the code
Finally, in order to improve the user experience, we also need to make the drop-down box support keyboard events oh ~ method is very simple, as shown below
public onKeydown(keyDownInfo: {index: number, code: number, e: KeyboardEvent}): void { const { code, e } = keyDownInfo; e.stopPropagation(); If (code === 38) {// on keyboard e.preventDefault(); If (this.curindex > 0) {this.curindex --; }} else if (code === 40) {this.curIndex < this.searchData.length-1) {this.curindex+ +; }} else if (code === 13) {// Enter, i.e. the user clicks this.rulemodal.showmodal (); const curData = this.searchData[this.curIndex]; if (curData) { this.onSelectClick(curData); }} / / implement drop-down box of a scroll bar with the keyboard up and down keys move when the press of a const lis = document. QuerySelectorAll (' # item '); const curLiEle = lis[this.curIndex] as HTMLElement; const searchList = this.searchList.nativeElement; const liEleHeight = 32; Searchlist.scrolltop = curliele.offsetTop + liEleHeight - searchList.scrollTop = curliele.offsetTop + liEleHeight - searchList.clientHeight; }Copy the code
conclusion
In fact, the component of the cascade search, his universality may not be very strong, but in the process of implementation, some details of the universality of logic is relatively strong ~ HOPE that these details can give you in the development of some help ~❤