在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 相关文章推荐
分享几个超级震憾的图片特效
Jan 08 Javascript
jQuery DataTables插件自定义Ajax分页实例解析
Apr 28 Javascript
js select下拉联动 更具级联性!
Apr 17 Javascript
Mongoose经常返回e11000 error的原因分析
Mar 29 Javascript
Angular自定义组件实现数据双向数据绑定的实例
Dec 11 Javascript
js中this对象用法分析
Jan 05 Javascript
详解ES6通过WeakMap解决内存泄漏问题
Mar 09 Javascript
JS使用setInterval实现的简单计时器功能示例
Apr 19 Javascript
浅谈Vue 性能优化之深挖数组
Dec 11 Javascript
Three.js实现3D机房效果
Dec 30 Javascript
原生javascript自定义input[type=radio]效果示例
Aug 27 Javascript
react 生命周期实例分析
May 18 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
杏林同学录(五)
2006/10/09 PHP
php操作csv文件代码实例汇总
2014/09/22 PHP
DOM基础及php读取xml内容操作的方法
2015/01/23 PHP
PHP程序中的文件锁、互斥锁、读写锁使用技巧解析
2016/03/21 PHP
javascript mouseover、mouseout停止事件冒泡的解决方案
2009/04/07 Javascript
date.parse在IE和FF中的区别
2010/07/29 Javascript
奉献给JavaScript初学者的编写开发的七个细节
2011/01/11 Javascript
jquery text(),val(),html()方法区别总结
2013/11/04 Javascript
jquery插件开发之实现md5插件
2014/03/17 Javascript
javascript对JSON数据排序的3个例子
2014/04/12 Javascript
javascript判断并获取注册表中可信任站点的方法
2015/06/01 Javascript
JavaScript 性能优化小结
2015/10/12 Javascript
Angular.js与Bootstrap相结合实现表格分页代码
2016/04/12 Javascript
JS组件Bootstrap ContextMenu右键菜单使用方法
2016/04/17 Javascript
详解Javascript数据类型的转换规则
2016/12/12 Javascript
原生JS实现圆环拖拽效果
2017/04/07 Javascript
JS实现的简单表单验证功能完整实例
2017/10/14 Javascript
Angular2中监听数据更新的方法
2018/08/31 Javascript
浅析微信扫码登录原理(小结)
2018/10/29 Javascript
JQueryDOM之样式操作
2019/03/27 jQuery
Vue.js + Nuxt.js 项目中使用 Vee-validate 表单校验
2019/04/22 Javascript
python中urllib模块用法实例详解
2014/11/19 Python
python的构建工具setup.py的方法使用示例
2017/10/23 Python
Python操作MySQL模拟银行转账
2018/03/12 Python
对Python3中的print函数以及与python2的对比分析
2018/05/02 Python
对web.py设置favicon.ico的方法详解
2018/12/04 Python
pytorch之ImageFolder使用详解
2020/01/06 Python
python MultipartEncoder传输zip文件实例
2020/04/07 Python
CSS3实现千变万化的文字阴影text-shadow效果设计
2016/04/26 HTML / CSS
英国在线滑雪板和冲浪商店:The Board Basement
2020/01/11 全球购物
高中生毕业学习总结的自我评价
2013/11/14 职场文书
建筑班组长岗位职责
2014/01/02 职场文书
物业管理毕业生的自我评价
2014/02/17 职场文书
《鹬蚌相争》教学反思
2014/04/22 职场文书
股东出资证明书(正规版)
2014/09/24 职场文书
党员转正意见怎么写
2015/06/03 职场文书