以Web components为基础的下拉菜单组件
一、HTML元素的template
代码:
const templateElem = document.createElement("template");
templateElem.innerHTML = `
<div id="root">
<style>
.cs-select{
border: 0px;
width: 100%;
height: 100%;
position: relative;
display:flex;
align-items: center;
justify-content: center;
}
.cs-select:empty::before {
content: attr(placeholder);
}
.cs-select-box{
padding: 2px 2px 2px 2px;
border: 1px solid #000;
background: #fff;
height: 120px;
width: 120px;
overflow-y: scroll;
position: absolute;
z-index: 9999;
display: none
}
.cs-select-box li {
white-space: wrap;
}
.cs-select-box li:hover {
background-color: #e0e0e0;
}
.cs-select-box::-webkit-scrollbar{
height: 8px;
width: 8px;
}
.cs-select-box::-webkit-scrollbar-thumb {
border-radius: 10px;
border-style: dashed;
border-color: transparent;
border-width: 6px;
background-color: rgba(157, 165, 183, 0.4);
background-clip: padding-box;
}
.cs-select-box::-webkit-scrollbar-thumb:hover {
background: rgba(157, 165, 183, 0.7);
}
.cs-select-box input[type="radio"]{
display: none
}
.cs-select-box div:hover{
background-color: #e0e0e0;
}
</style>
<div class="cs-select"></div>
<div class="cs-select-box">
</div>
</div>
`
二、class类的核心代码
1、构造函数
constructor() {
super();
var oldOptions = [];
const shadow = this.attachShadow({
mode: 'open' });
const content = templateElem.content.cloneNode(true);
this.name = this.getAttribute("name");
this.cho = this.getAttribute("cho");
this.isAsso = this.getAttribute("isAsso");
this.url = this.getAttribute("url");
this.mapping = JSON.parse(this.getAttribute("mapping"));
if(this.getAttribute("url") === "" && this.getAttribute("getDataByLS") === "false") {
this.getOptions(oldOptions);}
else if(this.getAttribute("url") !== "" && this.getAttribute("getDataByLS") === "false"){
this.type = this.getAttribute("type");
this.getOptionsByURL(oldOptions, this.url, this.type, this.mapping);
}
else if(this.getAttribute("getDataByLS") === "true"){
this.nameforLS = this.getAttribute("nameforLS");
this.getOptionByLocalStorage(oldOptions, window.localStorage, this.nameforLS, this.mapping);
top.window.addEventListener('storage', event => {
if(event.key === this.nameforLS){
this.getOptionByLocalStorage(oldOptions, window.localStorage, this.nameforLS, this.mapping);
}
});
}
else if(this.getAttribute("getDataBySQLlite" === "true")){
this.SQLliteKey = this.getAttribute("SQLliteKey");
this.getOptionBySQLlite(oldOptions, this.SQLliteKey, this.mapping);
}
if(this.cho === "checkbox" && this.isAsso === "false"){
//为普通多选框
//调用插入数据函数
this.setBoxforCheckbox(content, oldOptions);
shadow.appendChild(content);
this.$sele = shadow.querySelector(".cs-select");//获得显示框
//为显示框添加cho属性
this.$sele.setAttribute("cho", this.cho);
this.$sele.setAttribute("name", this.name);
this.$box = shadow.querySelector(".cs-select-box");//获得下拉菜单
//加入事件监听
this.$sele.addEventListener('click', function(e){
e.preventDefault();
let pthis = $(this);
if(pthis.attr('show-select') == 'true'){
pthis.attr('show-select', 'false');
pthis.next().hide();
return;
} else {
pthis.attr('show-select', 'false');
pthis.attr('show-select', 'true');
pthis.next().show();
}
pthis.next().find('input').on('change',function(){
var $this = $(this);
var $sele = $this.parent().parent();//找到cs-select-box
$sele.prev().empty();
let divs = $sele.find('div');
for(let i = 0; i < divs.length; i++) {
if(divs.eq(i).find('input').prop('checked') && $sele.perv().html().length == 0){
$sele.prev().attr("style", "border: 0px;width: 100%;height: 100%;text-align: center;");
$sele.prev().append('' + divs.eq(i).find('span').html() + '');
}
else if(divs.eq(i).find('input').prop('checked')){
$sele.prev().attr("style", "border: 0px;width: 100%;height: 100%;text-align: left;");
$sele.prev().append(";" + divs.eq(i).find('span').html() + '');
}
}
});
//失焦逻辑(以name属性为唯一性区分,作为失焦事件的判断)
top.window.$(document).click(pthis,function(e){
if(e.target.getAttribute("name") !== pthis[0].getAttribute("name")){
pthis[0].setAttribute('show-select', 'false');
pthis.next().hide();
}
});
});
}
else if(this.cho === "radio" && this.isAsso === "false"){
//为普通单选框
this.setBoxforRadio(content, oldOptions);
shadow.appendChild(content);
this.$sele = shadow.querySelector(".cs-select");//获得显示框
//为显示框添加cho属性
this.$sele.setAttribute("cho", this.cho);
this.$sele.setAttribute("name", this.name);
this.$box = shadow.querySelector(".cs-select-box");//获得下拉菜单
//加入事件监听
this.$sele.addEventListener('click', function(e){
e.preventDefault();
let pthis = $(this);
if(pthis.attr('show-select') == 'true'){
pthis.attr('show-select', 'false');
pthis.next().hide();
return;
} else {
pthis.attr('show-select', 'false');
pthis.attr('show-select', 'true');
pthis.next().show();
}
//失焦逻辑(以name属性为唯一性区分,作为失焦事件的判断)
top.window.$(document).click(pthis,function(e){
if(e.target.getAttribute("name") !== pthis[0].getAttribute("name")){
pthis[0].setAttribute('show-select', 'false');
pthis.next().hide();
}
});
});
}
else if(this.cho == "checkbox" && this.isAsso === "true"){
//为联想多选框
//由于后续会对下拉数据做处理,且不能删除原来的oldOptions列表,故维持一个新的数组用于联想的下拉
var newOptions = oldOptions;//这个数组最开始应该等于oldOptions
this.$sele = content.querySelector(".cs-select");//获得显示框
//为显示框添加cho属性
this.$sele.setAttribute("cho", this.cho);
this.$sele.setAttribute("name", this.name);
var input = document.createElement("textarea");
input.setAttribute("type", "text");
input.setAttribute("style", "border: 0;height: 100%;width: 100%;resize:none;text-align:center;vertical-align:middle");
this.$sele.appendChild(input);
//为cs-select内的input检查有无添加placeholder文本(有则预显示,无则不显示预加载文本)
if(this.getAttribute("placeholder") !== ""){
$(this.$sele).find("textarea").attr("placeholder", this.getAttribute("placeholder"));
}
this.$box = content.querySelector(".cs-select-box");//获得下拉菜单
//为下拉框初始化内容
this.setBoxforAsso("checkbox", content, oldOptions, newOptions, $(this.$sele).find("textarea").val(), this.$sele, this.$box);
shadow.appendChild(content);
//调整下拉菜单最大高度
$(this.$box).css({
'max-height': '300px',
'background-color': 'white',
'overflow-y': 'scroll',
'z-index': '91'
});
//输入文本时调用展示下拉菜单函数
$(this.$sele).find("textarea").on('input propertychange', ()=>{
var pthis = $(this.$sele).find("textarea");
var tag = pthis.val();
var tagstr = tag.split(/[,,,,;,;, ]/)[tag.split(/[,,,,;,;, ]/).length - 1];
if (tagstr != "") {
$(this.$sele).next().find("ul").empty();
this.setBoxforAsso("checkbox", content, oldOptions, newOptions, tagstr, this.$sele, this.$box);
}
else{
$(this.$sele).next().find("ul").empty();
this.setBoxforAsso("checkbox", content, oldOptions, newOptions, tagstr, this.$sele, this.$box);
}
});
//为cs-select添加事件监听(点击就显示下拉菜单、输入改变时修正菜单内容)
this.$sele.addEventListener('click', function(e){
e.preventDefault();
let pthis = $(this);
if(pthis.attr('show-select') == 'true'){
pthis.attr('show-select', 'false');
pthis.next().hide();
return;
} else {
pthis.attr('show-select', 'false');
pthis.attr('show-select', 'true');
pthis.next().show();
}
//失焦逻辑(以name属性为唯一性区分,作为失焦事件的判断)
top.window.$(document).click(pthis,function(e){
if(e.target.getAttribute("name") !== pthis[0].getAttribute("name")){
pthis[0].setAttribute('show-select', 'false');
pthis.next().hide();
}
});
});
}
else if(this.cho == "radio" && this.isAsso === "true"){
//为联想单选框
//由于后续会对下拉数据做处理,且不能删除原来的oldOptions列表,故维持一个新的数组用于联想的下拉
var newOptions = oldOptions;//这个数组最开始应该等于oldOptions
this.$sele = content.querySelector(".cs-select");//获得显示框
//为显示框添加cho属性
this.$sele.setAttribute("cho", this.cho);
this.$sele.setAttribute("name", this.name);
var input = document.createElement("input");
input.setAttribute("type", "text");
input.setAttribute("style", "border: 0;height: 100%;width: 100%;");
this.$sele.appendChild(input);
//为cs-select内的input检查有无添加placeholder文本(有则预显示,无则不显示预加载文本)
if(this.getAttribute("placeholder") !== ""){
$(this.$sele).find("input").attr("placeholder", this.getAttribute("placeholder"));
}
this.$box = content.querySelector(".cs-select-box");//获得下拉菜单
//为下拉框初始化内容
this.setBoxforAsso("radio", content, oldOptions, newOptions, $(this.$sele).find("input").val(), this.$sele, this.$box);
shadow.appendChild(content);
//调整下拉菜单最大高度
$(this.$box).css({
'max-height': '300px',
'background-color': 'white',
'overflow-y': 'scroll',
'z-index': '91'
});
//输入文本时调用展示下拉菜单函数
$(this.$sele).find("input").on('input propertychange', ()=>{
var pthis = $(this.$sele).find("input");
if (pthis.val() != "") {
this.setBoxforAsso("radio", content, oldOptions, newOptions, pthis.val(), this.$sele, this.$box);
}
else{
this.setBoxforAsso("radio", content, oldOptions, newOptions, pthis.val(), this.$sele, this.$box);
}
});
//为cs-select添加事件监听(点击就显示下拉菜单、输入改变时修正菜单内容)
this.$sele.addEventListener('click', function(e){
e.preventDefault();
let pthis = $(this);
if(pthis.attr('show-select') == 'true'){
pthis.attr('show-select', 'false');
pthis.next().hide();
return;
} else {
pthis.attr('show-select', 'false');
pthis.attr('show-select', 'true');
pthis.next().show();
}
//失焦逻辑(以name属性为唯一性区分,作为失焦事件的判断)
top.window.$(document).click(pthis,function(e){
if(e.target.getAttribute("name") !== pthis[0].getAttribute("name")){
pthis[0].setAttribute('show-select', 'false');
pthis.next().hide();
}
});
});
//失焦逻辑(以name属性为唯一性区分,作为失焦事件的判断)
top.window.$(document).click(pthis,function(e){
if(e.target.getAttribute("name") !== pthis[0].getAttribute("name")){
pthis[0].setAttribute('show-select', 'false');
pthis.next().hide();
}
});
}
}
2.获取数据方式
(1)从<option>标签:
代码:
getOptions(oldOptions) {
//从option标签获取选项,重新加入到oldOptions里
var options = this.querySelectorAll("option");
for(var i = 0; i < options.length; i++){
var tmp = new Object();
let value = options[i].value;
let name = options[i].innerHTML;
tmp.value = value;
tmp.name = name;
oldOptions.push(tmp);
}
}
(2)从网络请求:
代码:
getOptionsByURL(oldOptions, url, type, mapping) {
//从对应的top.getDataByTag函数获取选项,重新加入到oldOptions及box里
var urlpath = url;//后端给的接口
var data;
$.ajax({
url: top.rootIp + '/' + urlpath,
type: type,
data: data,
dataType: 'json',
async: false,
beforeSend: function (xhr) {
xhr.setRequestHeader("x-access-tokens", `${
top.loginToken}`);
},
success: function (res) {
if(res.success == false){
}
else if(res.success != undefined){
data = res['data'];
var nametag = mapping['name'];
var idtag = mapping['id'];
for (let i = 0; i < data.length; i++) {
var tmp = new Object();
let value = data[i][idtag];
let name = data[i][nametag];
tmp.value = value;
tmp.name = name;
oldOptions.push(tmp);
}
}
},
error: function (e) {
if(obj.error != undefined){
obj.error(e);
}
top.log('network_error',e);
}
});
}
(3)从localStorage获取:
代码:
getOptionByLocalStorage(oldOptions, localStorage, nameforLS, mapping){
var data;
var nametag = mapping['name'];
var idtag = mapping['id'];
var stringdata = localStorage.getItem(nameforLS);
data = JSON.parse(stringdata);
for(var i = 0; i < data.length; i++){
var tmp = new Object();
let value = data[i][idtag];
let name = data[i][nametag];
tmp.value = value;
tmp.name = name;
oldOptions.push(tmp);
}
}
(4)从SQLlite获取:
代码:
getOptionBySQLlite(oldOptions, SQLliteKey, mapping) {
//should attention 依赖于top.selectOfflineServerData的实现(从本地数据库SQLlite获取数据)
top.selectOfflineServerData(url, function(data){
var nametag = mapping['name'];
var idtag = mapping['id'];
for (let i = 0; i < data.length; i++) {
var tmp = new Object();
let value = data[i][idtag];
let name = data[i][nametag];
tmp.value = value;
tmp.name = name;
oldOptions.push(tmp);
}
})
}
3.在外界拿到数据后设置内部下拉框中的选项
//多选下拉菜单的设置:
setBoxforCheckbox(content, oldOptions){
//将获取好的内容设置到下拉菜单div中
content.querySelector(".cs-select-box").innerHTML = "";//首先清空,避免重复加入
for(var i=0; i<oldOptions.length; i++){
//循环加入
var oneDiv = document.createElement("div");
oneDiv.setAttribute("style", "float: left;");
oneDiv.innerHTML = "<input type='checkbox' value='" + oldOptions[i]['value'] + "' name='checkbox' id='checkbox_" +
oldOptions[i]['value'] + "'/><span>" + oldOptions[i]['name'] + "</span>";
content.querySelector(".cs-select-box").appendChild(oneDiv);
}
}
//单选下拉菜单的设置:
setBoxforRadio(content, oldOptions){
//同上
content.querySelector(".cs-select-box").innerHTML = "";//首先清空,避免重复加入
var oneDiv = document.createElement("div");
oneDiv.setAttribute("style", "float: left;width: 100%;");
oneDiv.innerHTML = "<input type='radio' value='' name='radio' id='radio_-1'/><span>请选择</span>";
content.querySelector(".cs-select-box").appendChild(oneDiv);
for(var i = 0; i<oldOptions.length; i++){
var oneDiv = document.createElement("div");
oneDiv.setAttribute("style", "float: left;width: 100%;");
oneDiv.innerHTML = "<input type='radio' value='" + oldOptions[i]['value'] + "' name='radio' id='radio_" +
oldOptions[i]['value'] + "'/><span οnclick=''>" + oldOptions[i]['name'] + "</span>";
content.querySelector(".cs-select-box").appendChild(oneDiv);
}
//为每个div绑定事件
var divList = $(content.querySelector("div.cs-select-box")).find("div");
for(var i = 0; i < divList.length; i++){
divList.eq(i).click(function(){
$(this).find("input").attr("checked",true);
$(this).parent().prev().attr("style", "border: 0px;width: 100%;height: 100%;text-align: center;");
$(this).parent().prev().html($(this).find("span").html());
$(this).parent().prev().attr('show-select', 'false');
$(this).parent().hide();
});
}
}
/联想输入下拉的设置判断(区分单、多选)
setBoxforAsso(str, oldOptions, newOptions, tag, box){
if(str === "radio"){
this.showBoxAndsetRadio(oldOptions, newOptions, tag, box);
}
else if(str === "checkbox"){
this.showBoxAndsetCheckbox(oldOptions, newOptions, tag, box);
}
}
4.自定义标签外部读取选中值与设置选中值(get value()、set value(value))
//get value 和set value
set value(str){
var strList = str.split(/[ ,,,;,;, ]/);
if(this.shadowRoot.querySelector("#root > div.cs-select > input") === null && this.shadowRoot.querySelector("#root > div.cs-select > textarea") === null){
var optionList = this.shadowRoot.querySelector("#root > div.cs-select-box").querySelectorAll("div");
var j = 0;
this.shadowRoot.querySelector("#root > div.cs-select").innerHTML = "";
for(var i = 0; i < optionList.length; i++){
if(strList[j] === optionList[i].querySelector("span").innerHTML){
optionList[i].querySelector("input").checked = true;//选中选项
//为上方显示框加入字符串
if(j != strList.length -1 && strList.length > 1){
this.shadowRoot.querySelector("#root > div.cs-select").innerHTML += (strList[j] + ";");
}
else if(strList.length === 1){
this.shadowRoot.querySelector("#root > div.cs-select").setAttribute("style", "border: 0px;width: 100%;height: 100%;text-align: center;");
this.shadowRoot.querySelector("#root > div.cs-select").innerHTML += strList[j];
}
else{
this.shadowRoot.querySelector("#root > div.cs-select").innerHTML += strList[j];
}
j++;
}
}
}
else if(this.shadowRoot.querySelector("#root > div.cs-select > input") !== null){
$(this.shadowRoot.querySelector("#root > div.cs-select > input")).val("");
for(var i = 0; i < strList.length; i++){
$(this.shadowRoot.querySelector("#root > div.cs-select > input")).val($(this.shadowRoot.querySelector("#root > div.cs-select > input")).val() + ";" + strList[i]);
}
}
else if(this.shadowRoot.querySelector("#root > div.cs-select > textarea") !== null){
$(this.shadowRoot.querySelector("#root > div.cs-select > textarea")).val("");
for(var i = 0; i < strList.length; i++){
$(this.shadowRoot.querySelector("#root > div.cs-select > textarea")).val($(this.shadowRoot.querySelector("#root > div.cs-select > textarea")).val() + ";" + strList[i]);
}
}
}
get value(){
var str="";
if(this.shadowRoot.querySelector("#root > div.cs-select > input") === null && this.shadowRoot.querySelector("#root > div.cs-select > textarea") === null){
var optionList = this.shadowRoot.querySelector("#root > div.cs-select-box").querySelectorAll("div");
for(var i=0; i<optionList.length; i++){
if(optionList[i].querySelector("input").checked) str += (optionList[i].querySelector("span").innerHTML + " ");
}
return str;
}
else if(this.shadowRoot.querySelector("#root > div.cs-select > input") !== null){
return $(this.shadowRoot.querySelector("#root > div.cs-select > input")).val();
}
else if(this.shadowRoot.querySelector("#root > div.cs-select > textarea") !== null){
return $(this.shadowRoot.querySelector("#root > div.cs-select > textarea")).val();
}
}
三、将上面的Select类与自定义标签关联:
window.customElements.define('cs-select', Select);
四、使用方法
引入JS
<script src="../ce-select.js"></script>
1.语法解释(带[]为可选字段)
<cs-select [style="height: 100%;width: 100%;"]
name="theName" placeholder="请输入"
cho="radio/checkbox" isAsso="false/true"
url="/oneSend" [type="get/post"]
getDataByLS="false/true" [nameforLS="oneNameforLS"]
getDataBySQLlite="false/true" [SQLliteKey="oneKeyforSQLlite"]
mapping='{}'>
<option></option>
......
</cs-select>
(1)name:
用于与其他同标签区分的标识属性,必须一个下拉唯一对应一个name;
(2)placeholder:
当显示框为空时的默认文本。
(3)cho:
用于区分是多选还是单选,其中radio对应单选;checkbox对应多选;
(4)isAsso:
区分是否为联想输入的标志,联想输入的下拉开发时用的,false代表不是联想输入,true代表联想输入;
(5)url:
网络请求链接,其在内部的使用方式是交给$.ajax,其返回值应为一个选项value与name对应的字典:
{
data:[
{
"valueOfId": "number_XXX","valueOfName": "name_XXX"},
{
"valueOfId": "number_XXX","valueOfName": "name_XXX"},
{
"valueOfId": "number_XXX","valueOfName": "name_XXX"},
......
]
success: true
}
或者:
{
data: [],
success: false
}
(6)type:
这个是前面URL的获取方式,有“get”和“post”两种(如果前面URL属性值为空,该属性可不用指明)
(7)getDataByLS:
是否从localStorage获取数据的标志,有false和true两种。
(8)nameforLS:
代表从localStorage的哪个关键字获取数据(如果getDataByLS属性值为false,可不用指明)。
(7)getDataBySQLlite:
是否从SQLlite获取数据的标志,有false和true两种。
(8)SQLliteKey:
代表从SQLlite的哪个关键字获取数据(如果getDataBySQLlite属性值为false,可不用指明)。
(9)mapping:
格式是一个json,表示映射关系,用来避免不同网络请求获得的data中的id和name字段key与class内部使用的不一致的问题,class内部默认为id和name,如果要更改class内部获取数据时取得字段key,则在mapping属性注明。
!!!注意:如果要使用默认key映射关系,mapping属性必须设置为{}:
mapping="{}"
(10) <option></option>标签:作为加载到下拉框的选项用的,必须有value属性(可以为空)。
value属性值会作为内部下拉选项对应的id,<option></option>内部的文本会作为下拉选项的<span></span>中的文本。
2.使用标签为自定义下拉框加入选项
例:
<option value="0">str0</option>
<option value="1">str1</option>
<option value="2">str2</option>
3.使用URL为<cs-select></cs-select>加入下拉选项:
<cs-select
name="theName" placeholder="请输入"
cho="radio" isAsso="false"
url="/getdata" type="get"
getDataByLS="false"
getDataBySQLlite="false"
mapping='{
"id":"xuhao","name":"xuanxiangmingcheng"}'>
</cs-select>