在Angular中实现一个级联效果的下拉框的示例代码


Posted in Javascript onMay 20, 2020

实现一个具有级联效果的下拉搜索框,实现的结果如下图所示

在Angular中实现一个级联效果的下拉框的示例代码

我们主要通过这个组件,来学习一些细微的逻辑,比如: 如何计算input框内文字的长度; 如何获取光标的位置;如何实现滚动条随着上下键盘的按动进行移动......

具体需求如下

  1. 级联搜索最多不超过三级,以”.“作为级联搜索的连接符
  2. 搜索框跟着文本框中的”.“进行向后移动,向右移动的最大距离不能超过文本框的宽度
  3. 当用户修改之前的级联内容,则不进行搜索,并隐藏搜索框;若用户在之前输入的是”.“, 则将此”.“之后的内容全部删除并搜索当前的相关内容

接下来我们根据需求,来写我们的逻辑

首先我们搭建html页面

<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 !== 0">
       <ul>
       // 这里在上篇文章中已经讲解过,如何实现让匹配的文字高亮显示~
        <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>
.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;
}

实现相关的逻辑

根据前两个需求,我们需要根据文本框中的”.“进行向后移动,向右移动的最大距离不能超过文本框的宽度。

思路: 我们需要将文本框中的字符串根据”.“来转换成数组,并且要想办法获取文本框中文字的长度。
如何获取文本框中文字的长度呢?
我们可以将文字的内容,重新放到一个display: inline-block的div容器中,然后获取容器的宽度,如下代码所示~

// html
 <!-- 用于测量input框的文字宽度 -->
 <div class="hidden-box" #firstLevel></div> // 以”.“转化的数组,下标为0的内容的宽度
 <div class="hidden-box" #secondLevel></div> // 以”.“转化的数组,下标为1的内容的宽度
 <div class="hidden-box" #allLevel></div> // 整个文本框的文字的宽度
 
 // ts
 import { ElementRef, 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('.') : [];

  // 将input中的内容,根据”.“转换成数组之后,将相关的内容赋值到新的div容器中,为了便于获取文字的宽度
  allLevel.innerHTML = targetValue;
  firstLevel.innerHTML = targetValueArr && targetValueArr[0];
  secondLevel.innerHTML = targetValueArr && targetValueArr.length > 1 ? targetValueArr[1] : '';

  // 得到相关宽度之后,实现下拉框移动的逻辑
  if (firstLevel.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`);
  }
 }
 }

到这里,我们可以完成第一和第二个需求,我们再来看看第三个需求: 主要是根据用户输入的位置以及修改的内容,来决定是否显示搜索和显示下拉框,如果用户输入的不是”.“我们则不显示,如果用户在之前的级联中输入”.“我们就需要进行再次帮用户搜索结果。

思路: 要想完成需求三,我们需要知道用户到底是在哪里操作,即我们要是可以知道光标的位置就更完美了~

// 获取光标的位置
 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;
  }

 // 用户input的时候调用的核心方法
 public handleSearchList(value: string): void {
  this.curIndex = 0;
  const cursorPosition = this.getCursorPosition(this.targetInput.nativeElement); // 获取光标位置
  let targetValue = value;
  const targetValueArr = targetValue ? targetValue.split('.') : [];
  const valueArrLength = targetValueArr.length;
  this.setSearchListPosition(targetValue); // 调整位置
  // 判断那些情况下应该搜索并显示下拉框
  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;
    ...获取下拉框中的数据
  } else {
   this.hidePopup();
  }
 }

最后为了更好的提高用的体验,我们还需要让下拉框支持键盘事件哦~方法也很简单,如下所示

public onKeydown(keyDownInfo: {index: number, code: number, e: KeyboardEvent}): void {
  const { code, e } = keyDownInfo;
  e.stopPropagation();
  if (code === 38) { // 键盘上
   e.preventDefault(); // 防止光标由最后边移动到前边,只是在开发中遇到的一点体验上小问题
   if (this.curIndex > 0) {
    this.curIndex--;
   }
  } else if (code === 40) { // 键盘下
   if (this.curIndex < this.searchData.length - 1) {
    this.curIndex++;
   }
  } else if (code === 13) {  // 回车,即相当于用户点击
   this.ruleModal.showModal();
   const curData = this.searchData[this.curIndex];
   if (curData) {
    this.onSelectClick(curData);
   }
  }
  // 实现下拉框的滚动条随着键盘的上下键按动时一起移动
  const lis = document.querySelectorAll('#item');
  const curLiEle = lis[this.curIndex] as HTMLElement;
  const searchList = this.searchList.nativeElement;
  const liEleHeight = 32;
  //(当前选中li标签的offsetTop + li标签自身的高度 - 下拉框的高度)
  searchList.scrollTop = curLiEle.offsetTop + liEleHeight - searchList.clientHeight;
 }

总结

其实这个级联搜索的组件,他的通用性可能并不是很强,但是在实现的过程中,一些细节逻辑的通用性还是比较强的~ 希望这些细节可以给同在开发中的你带来一些帮助~❤

到此这篇关于在Angular中实现一个级联效果的下拉框的示例代码的文章就介绍到这了,更多相关Angular级联下拉框内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
基本jquery的控制tabs打开的数量的代码
Oct 17 Javascript
jQuery 插件仿百度搜索框智能提示(带Value值)
Jan 22 Javascript
js获取本机的外网/广域网ip地址完整源码
Aug 12 Javascript
js function定义函数的几种不错方法
Feb 27 Javascript
JSP中使用JavaScript动态插入删除输入框实现代码
Jun 13 Javascript
jQuery实现的AJAX简单弹出层效果代码
Nov 26 Javascript
Angular开发者指南之入门介绍
Mar 05 Javascript
vuejs实现ready函数加载完之后执行某个函数的方法
Aug 31 Javascript
Windows下Node爬虫神器Puppeteer安装记
Jan 09 Javascript
使用Webpack提升Vue.js应用程序的4种方法(翻译)
Oct 09 Javascript
vue中的v-model原理,与组件自定义v-model详解
Aug 04 Javascript
QT与javascript交互数据的实现
May 26 Javascript
vue模块移动组件的实现示例
May 20 #Javascript
vue父子组件间引用之$parent、$children
May 20 #Javascript
jQuery HTML获取内容和属性操作实例分析
May 20 #jQuery
微信小程序国际化探索实现(附源码地址)
May 20 #Javascript
jQuery HTML设置内容和属性操作实例分析
May 20 #jQuery
jquery html添加元素/删除元素操作实例详解
May 20 #jQuery
jQuery HTML css()方法与css类实例详解
May 20 #jQuery
You might like
yii分页组件用法实例分析
2015/12/28 PHP
Codeigniter控制器controller继承问题实例分析
2016/01/19 PHP
Zend Framework教程之Zend_Db_Table表关联实例详解
2016/03/23 PHP
php处理复杂xml数据示例
2016/07/11 PHP
YII框架页面缓存操作示例
2019/04/29 PHP
php获取目录下所有文件及目录(多种方法)(推荐)
2019/05/14 PHP
前端开发必须知道的JS之原型和继承
2010/07/06 Javascript
用jquery与css打造个性化的单选框和复选框
2010/10/20 Javascript
js post提交调用方法
2014/02/12 Javascript
javascript实现3D切换焦点图
2015/10/16 Javascript
nodejs简单实现操作arduino
2016/09/25 NodeJs
Vue数据驱动模拟实现2
2017/01/11 Javascript
理解 javascript 中的函数表达式与函数声明
2017/07/07 Javascript
AngularJS select设置默认值的实现方法
2017/08/25 Javascript
elementUI 设置input的只读或禁用的方法
2018/10/30 Javascript
jQuery实现网页拼图游戏
2020/04/22 jQuery
详解Python 2.6 升级至 Python 2.7 的实践心得
2017/04/27 Python
Python实现的多进程和多线程功能示例
2018/05/29 Python
Python实现EXCEL表格的排序功能示例
2019/06/25 Python
Python类中self参数用法详解
2020/02/13 Python
python和js交互调用的方法
2020/06/23 Python
Merrell迈乐澳大利亚网站:购买户外登山鞋
2017/05/28 全球购物
英国领先的在线旅游和休闲零售商:lastminute.com
2019/01/23 全球购物
屈臣氏乌克兰:Watsons UA
2019/10/29 全球购物
Java程序员面试题
2016/09/27 面试题
数学系个人求职信范文
2014/01/30 职场文书
大学活动总结范文
2014/04/29 职场文书
设计专业毕业生求职信
2014/06/25 职场文书
班级团队活动方案
2014/08/14 职场文书
十佳少先队员演讲稿
2014/09/12 职场文书
信息合作协议书
2014/10/09 职场文书
行政撤诉申请书
2015/05/18 职场文书
2015年体育教学工作总结
2015/05/20 职场文书
2016元旦主持人经典开场白台词
2015/12/03 职场文书
会议承办单位欢迎词
2019/07/09 职场文书
Golang 1.18 多模块Multi-Module工作区模式的新特性
2022/04/11 Golang