以Web components为基础的下拉单选多选、联想单选输入、联想多选输入菜单组件(cs-select)

一、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>

4.5.走localStorage和SQLlite的使用语法和2.相近

五、例子(略)

猜你喜欢

转载自blog.csdn.net/weixin_47278656/article/details/130020831