在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 相关文章推荐
js AspxButton的客户端操作
Jun 26 Javascript
新发现一个骗链接的方法(js读取cookies)
Jan 11 Javascript
JS完整获取IE浏览器信息包括类型、版本、语言等等
May 22 Javascript
Bootstrap每天必学之导航组件
Apr 25 Javascript
JavaScript编写九九乘法表(两种任选)
Feb 04 Javascript
使用jQuery操作DOM的方法小结
Feb 27 Javascript
详解如何构建一个Angular6的第三方npm包
Sep 07 Javascript
微信小程序之swiper滑动面板用法示例
Dec 04 Javascript
js比较两个单独的数组或对象是否相等的实例代码
Apr 28 Javascript
微信小程序中为什么使用var that=this
Aug 27 Javascript
layui前端时间戳转化实例
Nov 15 Javascript
Vue 解决在element中使用$notify在提示信息中换行问题
Nov 11 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
PHP 5.0对象模型深度探索之对象复制
2008/03/27 PHP
php中去除所有js,html,css代码
2010/10/12 PHP
PHP关联链接常用代码
2012/11/05 PHP
PHP学习笔记之php文件操作
2016/06/03 PHP
PHP实现将几张照片拼接到一起的合成图片功能【便于整体打印输出】
2017/11/14 PHP
静态的动态续篇之来点XML
2006/12/23 Javascript
基于JQuery的6个Tab选项卡插件
2010/09/03 Javascript
JS获取并操作iframe中元素的方法
2013/03/21 Javascript
node.js中的http.createClient方法使用说明
2014/12/15 Javascript
javascript获取元素离文档各边距离的方法
2015/02/13 Javascript
jQuery中cookie插件用法实例分析
2015/12/04 Javascript
jQuery鼠标悬浮链接弹出跟随图片实例代码
2016/01/08 Javascript
jQuery实现弹出带遮罩层的居中浮动窗口效果
2016/09/12 Javascript
jQuery插件ajaxFileUpload使用详解
2017/01/10 Javascript
js实现省份下拉菜单效果
2017/02/15 Javascript
javascript 实现文本使用省略号替代(超出固定高度的情况)
2017/02/21 Javascript
Javascript实现信息滚动效果
2017/05/18 Javascript
将angular.js项目整合到.net mvc中的方法详解
2017/06/29 Javascript
浅谈Vue.js中的v-on(事件处理)
2017/09/05 Javascript
JavaScript禁止右击保存图片,禁止拖拽图片的实现代码
2020/04/28 Javascript
在Vuex中Mutations修改状态操作
2020/07/24 Javascript
JavaScript 几种循环方式以及模块化的总结
2020/09/03 Javascript
[18:16]sakonoko 2017年卡尔集锦
2018/02/06 DOTA
Python自动扫雷实现方法
2015/07/25 Python
python append、extend与insert的区别
2016/10/13 Python
django解决跨域请求的问题
2018/11/11 Python
详解Django定时任务模块设计与实践
2019/07/24 Python
python encrypt 实现AES加密的实例详解
2020/02/20 Python
UDP协议功能
2013/01/06 面试题
研发工程师岗位职责
2014/04/28 职场文书
委托证明模板
2014/09/16 职场文书
四风批评与自我批评发言稿
2014/10/14 职场文书
骆驼祥子读书笔记
2015/06/26 职场文书
初中化学教学反思
2016/02/22 职场文书
Python中22个万用公式的小结
2021/07/21 Python
vue代码分块和懒加载非必要资源文件
2022/04/11 Vue.js