js做一个带模糊搜索、自动补全的select组件auto-input-select

2024-09-01 17:20

本文主要是介绍js做一个带模糊搜索、自动补全的select组件auto-input-select,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

效果图:

思路

原本是想弄一个输入框input,挡在原生select的前面,结果发现,原生select无论怎么弄,都无法js手动控制展开下拉选,必须点击select,这就很尴尬

然后就只能弄一个输入框input,然后用ul生成下拉选,输入框聚焦打开下拉选,输入框失焦关闭下拉选,最后封装到一个自定义标签中实现了如图效果。

遇到问题

这里有一个问题,类似码值下拉选一般都是配合表单提交的,这里使用原生表单form的提交功能,我在自定义元素中提供了name属性,提供了value属性,还是无法让form提交时带上输入框的值。

因为搜不到解决方法,查看源码也找不到源码实现,因此目前是手动在同级位置插入一个隐藏的input标签,把name属性设置到input上,然后当value更新时同步更新input的值,这样表单提交时就会自动提交了,真是个大聪明

结果

我给它命名为

<auto-input-select></auto-input-select>

一共实现了两种效果,码值搜索(默认)和普通文本搜索

使用方式

使用方式很简单

(1)引入js脚本

<script src="auto-input-select.js"></script>

(2)页面上使用

<auto-input-select type="text" placeholder="请输入查询语言" data='["java","c++","python"]'></auto-input-select>

(3)提供js函数入口实现复杂功能

属性介绍

value设置值,当value变化时,会实时处理
data设置数据源,当data变化时,会实时处理
typecode默认(码值搜索),text(文本搜索)
placeholder设置提示信息
style设置整体样式
input-style设置input样式
item-style设置下拉选样式
select-max-height设置下拉选框的最大高度,默认是输入框的8倍,展示8个选项,超出部分滚动展示
load-max-num选项加载最大数量,默认200

js函数介绍

函数返回类型描述

setData( String | Array

         , Function 

         , Function )

void

设置下拉选数据源,Array字符串或者Array对象,

当type=code时:必须包含name和value

函数1:(item)=>{   return  String ; }  返回value的值; 当数组项种没有value属性,可以传递该函数设置

函数2:(item)=>{   return  String ; }  返回name的值; 当数组项种没有name属性,可以传递该函数设置

当type=text时:数组项不是String类型,会自动将内容转成String

findData( any )数组项返回一个根据value查询到的数据项
setItemStyle( Function )void(data,item,keyWord)=>{   return  String ; }    设置样式回调函数,允许用户根据选项数据和搜索词自由设置选项展示效果,返回html代码字符串
setValue( any )void设置value值,也可以直接访问value属性设置
getValue()value获取value值,也可以直接访问value属性获取
getKeyWord()String获取输入框的搜索词
searchName( String )void允许调用该方式手动触发搜索功能,在下拉框展开的时候,可以看到下拉选项同步变化
open()void允许手动打开下拉选框
close()void允许手动关闭下拉选框
isOpen()bool判断下拉框的打开状态

事件触发

input输入框的输入事件,可以使用标签属性oninput
focus输入框的聚焦事件,可以使用标签属性onfocus
blur输入框的失焦事件,可以使用标签属性onblur
select下拉选选项变化事件,可以使用标签属性onselect
open打开事件,标签属性onopen不管用,只能用addEventListener( "open" , Function )
close关闭事件,可以使用标签属性onclose

源代码

(function () {let tagName = 'auto-input-select';if (customElements.get(tagName)) {return; //避免多次引入报错}class AutoInputSelect extends HTMLElement {/*** 内部元素的dom,相当于document*/shadowRoot = null;/*** 构造参数*/constructor() {super();this.shadowRoot = this.attachShadow({mode: 'closed'});//元素内部的html不可见,为open时可见this.shadowRoot.innerHTML = `<style> :host { --item-height: 25px; } .container { display: inline-block; position: relative; width: 200px; height: var(--item-height); font-size: 13px; background-color: #a6e22e; } .container input { width: 100%; height: 100%; box-sizing: border-box; font-size: 13px; padding: 0 5px; outline: none; border-radius: 2px; border: 1px solid #DADADA; } .container input:focus { border: 1px solid #149bdf } .container>span{ display: none; position: absolute; cursor: pointer; width: 15px; height: 15px; top: calc(50% - 8px); color: #b6b6b6; right: 5px; border-radius: 7px; } .container:hover >span{ display: inline-block;background-color: white; } .container ul { display: none; position: absolute; top: 100%; left: 0; width: 100%; box-sizing: border-box; max-height: 800%; overflow-y: auto; border: 1px solid #ccc; border-radius: 4px; background-color: #fff; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); z-index: 1000; padding: 0; margin: 0; } .container ul li { list-style: none; line-height: var(--item-height); height: var(--item-height); cursor: pointer; padding: 0 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .container ul li:hover { background-color: #f0f0f0; } ::-webkit-scrollbar { height: 10px; width: 6px; } ::-webkit-scrollbar-thumb { background: #7f7f7f80; background-clip: padding-box; border: 1px solid transparent; border-radius: 10px; } </style> <div class="container"> <input type="text" autocomplete='off'> <span><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="currentColor" d="m466.752 512-90.496-90.496a32 32 0 0 1 45.248-45.248L512 466.752l90.496-90.496a32 32 0 1 1 45.248 45.248L557.248 512l90.496 90.496a32 32 0 1 1-45.248 45.248L512 557.248l-90.496 90.496a32 32 0 0 1-45.248-45.248z"></path><path fill="currentColor" d="M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768m0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896"></path></svg></span> <ul></ul> </div>`;}/*** 对外提供的标签属性*/type = 'code';//功能类型,目前有码值下拉(code默认)和文本匹配(text)两种data = null;//下拉选数据源,数组字符串,或者数组对象 ,必须包含name和valuevalue = null;//双向绑定的值style = null;//外壳样式placeholder = '--请选择--';//提示文本inputStyle = null;//设置输入框的样式itemStyle = null;//这个是直接追加在元素上的属性样式,将会直接追加在选项元素上selectMaxHeight = null;//下拉选的最大高度 可以是百分比,会参照输入框的高度展示loadMaxNum = 200;//选项数量加载限制,数量多了导致页面卡顿/*** 定制化* 可以定制一些参数,用于集成到系统中,同时修改下方初始化逻辑*//*** 注册并监控标签属性* 在这里定义,可以被监控数据变化,实时更新元素内容* 不在这里定义 也可以主动通过 this.getAttribute('style')获取指定属性值* 区别就是一个被动接收可以实时更新,一个主动获取*/static get observedAttributes() {return ['type', 'value', 'data', 'style', 'placeholder', 'input-style', 'item-style', 'select-max-height', 'load-max-num'];}/*** 这里用于处理监控到的标签属性变化* @param name 属性名称* @param oldValue 属性旧值* @param newValue 属性新值*/attributeChangedCallback(name, oldValue, newValue) {switch (name) {case 'type':this.type = newValue;break;case 'value':this.setValue(newValue);break;case 'style':this.style = newValue;if (this.isFinish) this.div.style.cssText = this.style;break;case 'data':this.setData(newValue);break;case 'placeholder':this.placeholder = newValue;if (this.isFinish) this.input.placeholder = newValue;break;case 'input-style':this.inputStyle = newValue;if (this.isFinish) this.input.style.cssText = this.inputStyle;break;case 'item-style':this.itemStyle = newValue;this.createItem(this.input.value);break;case 'select-max-height':this.selectMaxHeight = newValue;if (this.isFinish) this.ui.style.maxHeight = this.selectMaxHeight;break;case 'load-max-num':this.loadMaxNum = newValue;break;}}/*** 元素对象*/div = null;//外壳input = null;//输入框ui = null;//下拉选框/*** 着重解释:这是一个隐藏的input元素,用于代替本元素表单提交,当存在name属性时触发创建* 因为没有找到解决本元素参数绑定到表单的方法,只能创建一个隐藏的input插在页面上使用* 实时同步value到这个input中,代替本元素表单提交*/inputElement = null;/*** 选项内容样式回调,提供用户自定义* 回调入参(全量数据data,单个数据item,查询字符串keyWord)*/itemStyleCallback = null;//这个是设置选项展示内容的样式,不能控制选项元素本身/*** 事件标记*/isCreateItem = false;//是否正在创建下拉选项,避免多次调用冲突isFinish = false;//本元素的html是否渲染完成isFocus = false;//输入框是否聚焦/*** 当前选中数据项(手动指定值的时候,选项不存在时,会创建一个临时选项,解决码值越界也能正常读取写入value的问题)*/selectedItemData = null;/*** html渲染完成回调,做一些事件初始化,数据初始化操作*/connectedCallback() {// this.shadowRoot 这个是用来获取,本元素内部的html元素,与外部document隔离的this.div = this.shadowRoot.querySelector('div');this.input = this.div.children[0];this.ui = this.div.children[2];this.div.children[1].addEventListener('click', (event)=>{event.stopPropagation();this.setValue(null)this.createItem();});//根据name判断是否表单绑定if (this.getAttribute("name")) {this.inputElement = document.createElement('input');this.inputElement.name = this.getAttribute("name");this.inputElement.type = 'hidden';this.parentElement.appendChild(this.inputElement);}this.isFinish = true;//这个主要是为了标签属性值监控部分加的标识//追加样式if (this.style && this.style !== '') this.div.style.cssText = this.style;this.input.style.cssText = this.inputStyle;this.input.placeholder = this.placeholder;if (this.selectMaxHeight) this.ui.style.maxHeight = this.selectMaxHeight;//定制化逻辑,根据定制化属性初始化数据项,可在这写//初始化数据完成后,将初始化值绑定到本元素上,如输入框默认展示对应的选项this.setValue(this.value);//选项点击事件this.ui.addEventListener('click', (event) => {//有时候可能点中li内部的元素,这里循环查找let li = event.target;while (li.parentElement && li.tagName !== 'LI') {li = li.parentElement;}if (li.tagName === 'LI') {this.setValue(li.data, true);}event.stopPropagation();this.close();//点击选项后手动关闭下拉选});//将输入框的这些事件绑定到本元素上,用于给开发者使用this.input.addEventListener('input', this._handleInput);this.input.addEventListener('focus', this._handleFocus);this.input.addEventListener('blur', this._handleBlur);}//input与自定义元素事件绑定,转发_handleInput = (event) => {this.createItem(this.input.value);try {this.dispatchEvent(event);} catch (e) {}}_handleFocus = (event) => {this.isFocus = true;this.open();this.createItem(this.input.value);try {this.dispatchEvent(event);} catch (e) {}}_handleBlur = (event) => {this.isFocus = false;try {this.dispatchEvent(event);} catch (e) {}}/*** 自定义下拉选触发事件*/_handleChange = (data) => {this.dispatchEvent(new CustomEvent('select', {detail: data}));}_handleOpen() {this.dispatchEvent(new CustomEvent('open'));}_handleClose() {this.dispatchEvent(new CloseEvent('close'));}/***  根据搜索词创建匹配的下拉选项*/createItem(keyWord) {if (!this.isFinish || this.isCreateItem) {// console.log("取消操作:初始化未完成,或者多次同时创建");return;}this.isCreateItem = true;if (this.data == null) {this.data = [];}this.ui.innerHTML = "";this.ui.style.opacity=1;let count = 0;for (let item of this.data) {if (this.loadMaxNum <= count) break;if (!keyWord || this.getItemName(item).indexOf(keyWord) > -1) {let showHtml = this.getItemHtml(this.data, item, keyWord);const newElement = document.createElement('li');newElement.data = item;newElement.innerHTML = showHtml;newElement.style.cssText = this.itemStyle;this.ui.appendChild(newElement);count++;}}if(this.ui.innerHTML===''){this.ui.style.opacity=0;}this.isCreateItem = false;}//创建选项展示内容的html代码getItemHtml(data, item, keyWord) {if (this.itemStyleCallback) {return this.itemStyleCallback(data, item, keyWord)} else if (this.type === 'code') {return this.getString(item.name);} else if (this.type === 'text') {return this.getString(item);}}//转字符串getString(value) {if (!value) return '';// 判断值的类型if (typeof value === 'object') {// 如果是对象类型,转换为 JSON 字符串return JSON.stringify(value);} else {// 否则,直接调用 toString 方法转换为字符串return String(value);}}/*** 监听下拉选打开后的点击操作* (1)点击了下拉选选项,这里就不处理了,这里因为ul比document先一步拿到点击事件,且选中选项后会关闭下拉选,因此通过判断下拉选已经关闭,来判断点击了选项* (2)点击输入框里面,不做处理,因为输入框失焦比ul拿到点击还要早,因此这里通过判断输入框聚焦状态,来判断点击了输入框* (3)点击其他地方,关闭下拉选*/_handleClick = (event) => {if (!this.isFocus && this.isOpen()) {if (this.input.value === '') {this.setValue(null)} else {this.input.value = this.getItemName();}this.close();}}//手动打开下拉选open() {this.ui.style.display = 'block';document.addEventListener('click', this._handleClick);this._handleOpen();}//手动关闭下拉选close() {this.ui.style.display = 'none';document.removeEventListener('click', this._handleClick);this._handleClose();}isOpen() {return this.ui.style.display === 'block';}/*** 设置下拉选数据源* 格式[{name:'xxx',value:'xxx'}]* @param data  数据源* @param valueCallback  数据中没有value,需要自定义映射* @param nameCallback  数据中没有name,需要自定义映射*/setData(data, valueCallback, nameCallback) {try {if (typeof data === 'string') {data = JSON.parse(data);}if (!(data instanceof Array)) {console.warn("数据不合法,请提供数组数据,格式:[{name:'xxx',value:'xxx'},...,{name:'xxx',value:'xxx'}]");}if (nameCallback || valueCallback) {//有自定义映射for (const item of data) {if(valueCallback) item.value=valueCallback(item)if(nameCallback) item.name=nameCallback(item)if (this.value && this.value === item.value) this.setValue(item);this.data.push(item);}} else {//没有自定义映射this.data = data;if (this.value) this.setValue(this.value);}this.createItem();} catch (e) {console.warn("数据解析报错", data);throw new Error(e);}}//根据值进行数据搜索findData(value) {if (this.data == null) {return null;}for (let item of this.data) {if (value === item.value) return item;}return null;}//手动搜索searchName(keyWord) {this.createItem(keyWord);if (this.isFinish) this.input.value = keyWord;}getKeyWord(){return this.input.value;}//设置自定义选项样式回调setItemStyle(callback) {this.itemStyleCallback = callback;}//获取当前选中值的文本,换句话说是输入框的文本getItemName(item) {let itemTmp = this.selectedItemData;if (item) itemTmp = item;if (!itemTmp) return null;if (this.type === 'code') {return itemTmp.name || '';} else if (this.type === 'text') {return this.getString(itemTmp);}}//获取当前选中值getValue() {return this.value;}/*** 允许手动设置选中值* @param value  要设置的选中值* @param isItemData  表示传递的value是含value的数据项,无需去data中查询,数据量大的时候相当与小优化*/setValue(value, isItemData) {if (this.type === 'code') {if (isItemData) {this.selectedItemData = value;} else {let item = this.findData(value);if (item) {this.selectedItemData = item;} else {//空值,未匹配的值this.selectedItemData = {name: '', value: value}}}this.value = this.selectedItemData.value;} else if (this.type === 'text') {this.value = value;this.selectedItemData = value;}this._handleChange(this.selectedItemData);if (this.input) this.input.value = this.getItemName();if (this.inputElement) this.inputElement.value = this.getString(this.value);}// 获取自定义元素的值get value() {return this.getValue();}// 设置自定义元素的值/*** @param {any} val*/set value(val) {this.setValue(val);}}// 注册自定义元素到html中customElements.define(tagName, AutoInputSelect);
})();

高级使用

我在代码中留下了定制化关键字,当你会写自定义标签的代码时,可以根据位置,追加自定义代码,以便于集成到系统中去,如传递码值类型,在初识化的代码中读取码值类型,异步请求获取系统码值数据进行初始化,这样在系统中使用时,就无需写很多的js代码控制,很方便的实现自动补全下拉选。

文本模糊搜索功能也是一样道理。

注意是刚写的,未经充分验证,有问题欢迎指正

这篇关于js做一个带模糊搜索、自动补全的select组件auto-input-select的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1127576

相关文章

JS常用组件收集

收集了一些平时遇到的前端比较优秀的组件,方便以后开发的时候查找!!! 函数工具: Lodash 页面固定: stickUp、jQuery.Pin 轮播: unslider、swiper 开关: switch 复选框: icheck 气泡: grumble 隐藏元素: Headroom

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

hdu1240、hdu1253(三维搜索题)

1、从后往前输入,(x,y,z); 2、从下往上输入,(y , z, x); 3、从左往右输入,(z,x,y); hdu1240代码如下: #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#inc

如何在页面调用utility bar并传递参数至lwc组件

1.在app的utility item中添加lwc组件: 2.调用utility bar api的方式有两种: 方法一,通过lwc调用: import {LightningElement,api ,wire } from 'lwc';import { publish, MessageContext } from 'lightning/messageService';import Ca

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

2、PF-Net点云补全

2、PF-Net 点云补全 PF-Net论文链接:PF-Net PF-Net (Point Fractal Network for 3D Point Cloud Completion)是一种专门为三维点云补全设计的深度学习模型。点云补全实际上和图片补全是一个逻辑,都是采用GAN模型的思想来进行补全,在图片补全中,将部分像素点删除并且标记,然后卷积特征提取预测、判别器判别,来训练模型,生成的像

hdu 4517 floyd+记忆化搜索

题意: 有n(100)个景点,m(1000)条路,时间限制为t(300),起点s,终点e。 访问每个景点需要时间cost_i,每个景点的访问价值为value_i。 点与点之间行走需要花费的时间为g[ i ] [ j ] 。注意点间可能有多条边。 走到一个点时可以选择访问或者不访问,并且当前点的访问价值应该严格大于前一个访问的点。 现在求,从起点出发,到达终点,在时间限制内,能得到的最大

Node.js学习记录(二)

目录 一、express 1、初识express 2、安装express 3、创建并启动web服务器 4、监听 GET&POST 请求、响应内容给客户端 5、获取URL中携带的查询参数 6、获取URL中动态参数 7、静态资源托管 二、工具nodemon 三、express路由 1、express中路由 2、路由的匹配 3、路由模块化 4、路由模块添加前缀 四、中间件

AI基础 L9 Local Search II 局部搜索

Local Beam search 对于当前的所有k个状态,生成它们的所有可能后继状态。 检查生成的后继状态中是否有任何状态是解决方案。 如果所有后继状态都不是解决方案,则从所有后继状态中选择k个最佳状态。 当达到预设的迭代次数或满足某个终止条件时,算法停止。 — Choose k successors randomly, biased towards good ones — Close

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机