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代码控制,很方便的实现自动补全下拉选。

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/414291.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python-变量声明、数据类型、标识符

一.变量 1.什么是变量 为什么需要变量呢&#xff1f; 一个程序就是一个世界&#xff0c;不论使用哪种高级程序语言编写代码&#xff0c;变量都是其程序的基本组成单位。如下图所示的sum和sub都是变量。 变量的定义&#xff1a; 变量相当于内存中一个数据存储空间的表示&#…

【Unity小工具】Image组件宽度、高度自适应

Unity开发中&#xff0c;用同一个Image进行动态加载不同尺寸的图片&#xff0c;在显示上会有形变此工具可以进行Image的宽度、高度自适应 实现原理 获取Image原始尺寸&#xff08;sizeDelta&#xff09;获取图片原始尺寸&#xff08;spriteSizeDelta&#xff09;公式&#xff…

Git 忽略已经提交的文件

对于未提交过的文件直接用ignore文件即可,不再赘述 对于已经提交过的文件,但是实际上不需要的,可以用git rm --cached命令 比如下图这个 .vsconfig被我误提交了或者忘了在ignore里添加了 但是我实际上不想要这个文件,那么在项目根目录打开git bash ,输入 git rm --cached .vsc…

【Hot100】LeetCode—34. 在排序数组中查找元素的第一个和最后一个位置

目录 1- 思路二分 - 左侧二分 右侧二分 2- 实现⭐34. 在排序数组中查找元素的第一个和最后一个位置——题解思路 3- ACM 实现 原题链接&#xff1a;34. 在排序数组中查找元素的第一个和最后一个位置 1- 思路 二分 - 左侧二分 右侧二分 右区间二分 ——> 找首次出现的位置…

unreal engine5.4.3动画重定向

UE5系列文章目录 文章目录 UE5系列文章目录前言 前言 ue5.4和ue3动画重定向之间存在差异&#xff0c;跟ue5.2差别更大一点&#xff0c;总之ue5.4越来越简化动画重定向&#xff0c;不想之前还需要制作RTG文件 这是ue5.3.2的制作动画重定向的界面 这是ue5.4.2的制作动画重定向…

编译FFmpeg动态库

编译FFmpeg动态库 环境 macOS High SierraFFmpeg 4.3android-ndk-r21b 编译so库 下载FFmpeg4.3源代码&#xff0c;进入源码目录创建build_android.sh脚本&#xff0c;ffmpeg从4.0起新增了target-osandroid&#xff0c;所以不用再修改configure文件。 注意&#xff1a; ndk…

k8s1.23 部署Prometheus-Operator集群监控

1. Prometheus-Operator介绍 Prometheus Operator 为 Kubernetes 提供了对 Prometheus 相关监控组件的本地部署和管理方案&#xff0c;该项目的目的是为了简化和自动化基于 Prometheus 的监控栈配置&#xff0c;主要包括以下几个功能&#xff1a; kubernetes自定义资源&#…

【网络安全】服务基础第一阶段——第八节:Windows系统管理基础---- Web服务与虚拟主机

目录 一、WWW概述 1.1 HTML 1.2 URI与URL 1.2.1 URL&#xff08;统一资源标识符&#xff0c;Uniform Resource Locator&#xff09; 1.3 HTTP 1.3.1 HTTP请求&#xff1a; 1.3.2 HTTP响应 1.3.3 状态码 1.4常见Web URL格式 实验一、网站搭建 1&#xff09;访问失败可…

孩子自闭症的主要表现:探寻理解之门

自闭症&#xff0c;也称为孤独症&#xff0c;是一种复杂的神经发展障碍&#xff0c;它影响着孩子的社交互动、沟通能力以及行为模式。当家长注意到孩子出现自闭症倾向时&#xff0c;及时识别并寻求专业帮助至关重要。以下是孩子自闭症的一些主要表现&#xff0c;希望能为家长提…

温馨网站练习运用

第二次与团队一起制作网页虽然不进行商用&#xff0c;但是练习一下还是好的&#x1f60a;&#x1f60a; 我主要负责后端部分&#xff0c;该项目用了SpringBoot框架、SpringSecurity的安全框架、结合MyBatis-Plus的数据库查询。如果想看看&#xff0c;网站&#xff1a;温馨网登…

昇腾AI处理器的计算核心 - AI Core即DaVinci Core

昇腾AI处理器的计算核心 - AI Core即DaVinci Core flyfish 从一段代码的解释开始 template <typename T> class GlobalTensor { public:void setGlobalBuffer(T* buffer, uint32_t buffersize) {// 在这里实现设置全局缓冲区的逻辑} };语法的说明&#xff0c;主要用于…

SQLi-LABS靶场51-55通过攻略

第51关&#xff08;报错注入 闭合&#xff09; 查数据库 ?sort1%27%20and%20updatexml(1,concat(1,database()),3)-- 查表 ?sort1 and updatexml(1,concat(1,(select group_concat(table_name) from information_schema.tables where table_schemasecurity)),1)-- 第52关…

安装python软件

系统是32位还是64位 “此电脑"或者"我的电脑”&#xff0c;鼠标右键——属性&#xff0c;出现如下图查看电脑系统类型&#xff08;图中显示电脑系统类型是64位系统&#xff0c;安装Python则选择其名含有"adm64"字样的文件&#xff09;: 软件安装地址 全…

用AI生成旅游打卡照!FLUX假装去旅行lora的使用【附工作流】

hello&#xff01;今天我们来聊聊一个特别有意思的话题&#xff1a;如何用AI生成那些看起来像是去过世界各地的旅游打卡照&#xff0c;还能在朋友圈里炫耀一番。很多人看到这些照片都会问&#xff1a;“你真的去过这些地方吗&#xff1f;” 而且最主要的是这种图片做点自媒体旅…

提高工作效益方法(一)

目录 如何提高工作效率? 如何提高工作效率?&#xff08;每日工作安排&#xff09; 怎么在职场做好时间管理&#xff1f; 如何提高工作效率? 提高工作效率的关键在于采用一系列策略和方法&#xff0c;以确保工作能够高效、有序地进行。通过这些方法&#xff0c;可以有效地提…

银河麒麟编译opencv库并配置qt环境

1.opencv下载版本:opencv4.5.5,qt安装的是qt5.12.11,系统版本: 2.首先应该安装cmake工具: 下载地址:https://cmake.org/download/ 安装步骤: 1)解压; 2)进入解压后的文件夹cd cmake-3.30.2 3)./bootstrap 4)sudo make 5)sudo make install 3.下载opencv,下…

7个流行的开源数据治理工具

数字化时代&#xff0c;数据是已经成为最宝贵的资产之一。数据支撑着我们的政府、企业以及各类组织的所有流程&#xff0c;并为决策以及智能化服务提供支撑。大数据有大用途&#xff0c;但是也可能隐藏着巨大的风险&#xff0c;特别是如果我们对数据的情况不是很了解的时候&…

第二证券:两市成交不足5000亿元 小盘成长股逆势活跃

A股持续小幅颤动&#xff0c;银行等大盘蓝筹股呈现调整&#xff0c;小盘生长股则逆势反弹&#xff0c;创业板指、中证500、中证1000等指数小幅飘红。到收盘&#xff0c;沪指跌0.4%报2837.43点&#xff0c;深证成指跌0.31%报8078.82点&#xff0c;创业板指微涨0.05%报1531.45点&…

掌控安全CTF-2024年8月擂台赛-ez_misc

题解&#xff1a; 题目给了一个流量包和一个加密的zip文件&#xff0c;我们首先打开流量包&#xff0c;很多流量&#xff0c;查看一下http协议&#xff0c;发现是个sql靶场&#xff0c;找到关键字样flag&#xff0c;得到一串字符&#xff1a; LJWXQ2C2GN2DAYKHNR5FQMTMPJMDER…

hive学习(六)

一、函数 1.单行函数 特点&#xff1a;输入一行&#xff0c;输出一行&#xff08;一进一出&#xff09; 可分为日期函数&#xff0c;字符串函数&#xff0c;集合函数&#xff0c;数学函数和流程控制函数等 1&#xff09;算术运算函数 2&#xff09;数值函数 --round函数 …