能够发现在 select 中选拔了三个外表组件,那里面分页时查询条件有限支撑的题目又是最让自己头痛的工作

var list = $('#list').GridView({
            'apiUrl': '/Student/List',  // 指定数据请求的URL路径
            'apiType': 'post',  // 请求的方式
            'columns': [    // 要显示的列,title对应表头,column对应数据项的属性名称,width指定列的宽度,func指定绑定时调用的函数名称
                { title: '姓名', column: 'Name', width: 160 },
                { title: '年龄', column: 'Age' },
                { title: '性别', column: 'Sex', width: 100, 'func': 'convertToSex' }
            ],
            'valueColumn': 'StudentId', // data-value 取值的属性名
            'pageSize': 20, // 每页显示的数量
            'isMultiy': false,  // 是否支持多选
            'isTreeView': false,   // 是否支持树形
            'pager': 'pager',   // 指定包含分页的divid,主要是为了能单独控制pager中的一些数据,把pager给拆出来了,后来发现似乎用处不大
            'onRowClick': function(id) { }, // 当数据行被点击时执行的回调,参数是tr中的data-value
            'convertSource': function (data) { return data.body; }, // 使用数据源之前对数据进行转换。因为我的api返回的都是{ code: 200, body: [] }这种类型,需要在这里直接返回body
            'onDataBindComplete': function() {}, // 当数据加载完成,也就是列表渲染完成后的回调。比如说提醒用户加载完成之类的
            'getSearchData': function() { return $('#form1').serialize(); },    // 获取查询参数,这个很重要,想了很多办法,最终采用了这种方案,在查询前运行这个函数,将返回值作为ajax的查询参数
            'listCssClass': 'table',    // 列表table的样式名
            'pagerCssClass': 'pager',   // 分页最外面div的样式名
            'beforeSend': function() { }    // ajax请求之前调用的函数,原本是为了提醒一下加载已开始,请稍后之类的,现在没怎么用到
        });

什么浮现远程数据?

通过为 select 设置 remoteremote-method
属性来获取远程数据。remote-method 方法最终将数据赋值给 option 的
v-model 绑定数组数据将结果展现出来即可。

功能:

从多少个问题去看源码逻辑

根据个体的习惯和对效果的表达,定义了如此一个setting:

怎么着兑现搜索效果?

从 template 中可见,select 有四个input,一个用于体现结果,一个则用来查询检索。大家来看下搜索内容的 input
文本框怎样兑现搜索成效:
在 input 中有
@input="e => handleQueryChange(e.target.value)"那般一段代码。所以,handleQueryChange
方法就是关键所在了。

      // 处理查询改变
      handleQueryChange(val) {
        if (this.previousQuery === val) return;
        if (
          this.previousQuery === null &&
          (typeof this.filterMethod === 'function' || typeof this.remoteMethod === 'function')
        ) {
          this.previousQuery = val;
          return;
        }
        this.previousQuery = val;
        this.$nextTick(() => {
          if (this.visible) this.broadcast('ElSelectDropdown', 'updatePopper');
        });
        this.hoverIndex = -1;
        if (this.multiple && this.filterable) {
          const length = this.$refs.input.value.length * 15 + 20;
          this.inputLength = this.collapseTags ? Math.min(50, length) : length;
          this.managePlaceholder();
          this.resetInputHeight();
        }
        if (this.remote && typeof this.remoteMethod === 'function') {
          this.hoverIndex = -1;
          this.remoteMethod(val);
        } else if (typeof this.filterMethod === 'function') {
          this.filterMethod(val);
          this.broadcast('ElOptionGroup', 'queryChange');
        } else {
          this.filteredOptionsCount = this.optionsCount;
          this.broadcast('ElOption', 'queryChange', val);
          this.broadcast('ElOptionGroup', 'queryChange');
        }
        if (this.defaultFirstOption && (this.filterable || this.remote) && this.filteredOptionsCount) {
          this.checkDefaultFirstOption();
        }
      },

其中,remoteMethodfilterMethod
方法是自定义的长途查询和地点过滤方法。如若没有自定义的那多少个章程,则会触发广播给
optionoption-group 组件 queryChange 方法。

      // option.vue
      queryChange(query) {
        let parsedQuery = String(query).replace(/(\^|\(|\)|\[|\]|\$|\*|\+|\.|\?|\\|\{|\}|\|)/g, '\\$1');
        // 匹配字符决定是否显示当前option
        this.visible = new RegExp(parsedQuery, 'i').test(this.currentLabel) || this.created;
        if (!this.visible) {
          this.select.filteredOptionsCount--;
        }
      }

option 中通过正则匹配决定是不是隐身当前 option 组件,而 option-group
通过获取子组件,判断即使有子组件是可知的则显示,否则隐藏。

      // option-group.vue
      queryChange() {
        this.visible = this.$children &&
          Array.isArray(this.$children) &&
          this.$children.some(option => option.visible === true);
      }

就此,其实 option 和 option-group
在搜寻的时候只是隐藏掉了不匹配的情节而已。

OK,接下去就是其一增添的现实落实了,代码很多,但思路很不难,主要就是代码的拼接。我们自己看吗!

select 要促成的成效

参照法定文档的内容罗列出
select 的一对职能,前面跟上本人对效益完毕的知晓:

  • 单选 —— 点击 select 弹出下拉框,点击 option 完结赋值。
  • 禁用 —— selectoption 都有 disabled 选项用于禁用。
  • 清空 —— 如果 select 中有内容,鼠标悬浮在 input
    上突显删除图标,点击执行删除操作。
  • 多选(平铺浮现和数字彰显数量二种方法) —— 参数 model
    变为数组,点击下拉菜单中的选项添加或删除数组中的值。
  • 自定义模板 —— option 中定义了 slot 插槽,默认加了 span
    显示内容。可以修改 el-option 标签中内容出自定义模板。
  • 分组 —— 使用 option-group 组件来促成分组功用。
  • 搜索 —— 通过正则匹配搜索项,不切合查找项的主宰 v-show 隐藏
  • 创设条目 —— 在 select 中添加额外 option(一般 option 都是通过
    slot 插槽传递的),如允许创造条目,则浮现那条 option ,option
    的内容显示为查询内容。
(function ($) {

    $.fn.GridView = function (settings) {
        // 系统变量
        var self = this;
        self.selected = [];
        var pageindex = 1, pageSize = 20;

        if (settings.pageSize && $.isNumeric(settings.pageSize) && settings.pageSize > 0) {
            pageSize = settings.pageSize;
        }

        var rand = Math.floor(Math.random() * 1000);
        var table = $("<table" + (settings.listCssClass ? " class='" + settings.listCssClass + "'" : "") + " id='myList" + rand + "'></table>");

        var colgroup = colgroup2 = "<colgroup>"
        $.each(settings.columns, function (idx, item) {
            colgroup += "<col" + item.width ? " style='width:" + item.width + "px;'" : "" + ">";
            colgroup2 += "<col" + item.width ? " style='width:" + item.width + "px;'" : "" + ">";
        });
        colgroup += "<col style='width:18px;'></col>";
        colgroup += "</colgroup>";
        colgroup2 += "</colgroup>";
        var table = $("<table" + (settings.listCssClass ? " class='" + settings.listCssClass + "'" : "") + " id='myList" + rand + "'></table>");

        var pagerHtml = '<div' + (!settings.pagerCssClass ? '' : ' class="' + settings.pagerCssClass + '"') + '>';
        //var pagerHtml = '';
        pagerHtml += '<a class="disabled" id="first' + rand + '"><i class="fa fa-fast-backward"></i></a>';
        pagerHtml += '<a class="disabled" id="prev' + rand + '"><i class="fa fa-backward"></i></a>';
        pagerHtml += '<div class="pager-index"><b>第</b><input value="1" type="text" maxlength="4" id="index' + rand + '"><b>页</b></div>';
        pagerHtml += '<a class="disabled" id="next' + rand + '"><i class="fa fa-forward"></i></a>';
        pagerHtml += '<a class="disabled" id="last' + rand + '"><i class="fa fa-fast-forward"></i></a>';
        pagerHtml += '<div class="pager-info2"> 共计 0 条记录,';
        pagerHtml += '每页显示 ' + pageSize + ' 条,';
        pagerHtml += '共 1 页,';
        pagerHtml += '用时 0毫秒';
        pagerHtml += "</div></div>";
        var pagerDom = $(pagerHtml);
        var firstButton, prevButton, nextButton, lastButton, currentSpan, pageSizeSpan, totalCountSpan, totalPagesSpan, loadTimeSpan;

        // 临时变量
        var tbody, pager, cbAll, checkboxes, isPager = false, totalPage = 1;

        // 创建table
        self.append(table);

        // 创建thead
        addChildrenToTable();

        // 为tbody赋值
        tbody = $("#tbody" + rand);

        var colCount = settings.columns.length + 2;
        tbody.html("<tr class='empty'><td colspan='" + colCount + "'>等待加载数据...</td></tr>");

        // 创建分页
        if (settings.pager && $("#" + settings.pager)[0]) {
            pager = $("#" + settings.pager);
            isPager = true;
            pager.append(pagerDom);
            setPagerButtonEvent();
        }

        // 创建渲染函数
        self.fun = new Function("data", renderFunString());

        // 渲染第一列
        self.setFirstCol = function (val) {
            if (settings.isMulti) {
                return "<td class='chk'><input type='checkbox' id='cb" + val + "' value='" + val + "'></td>"
            } else {
                return "<td class='no'>" + val + "</td>";
            }
        }

        // 渲染最后一列
        self.setLastCol = function () {
            return "<td></td>";
        }

        // 渲染中间列
        self.setCol = function (content, width, cssClass, level) {
            var html = "<td";
            html += width ? " style='width:" + width + "px'" : "";
            html += cssClass && cssClass != 'undefined' ? " class='" + cssClass + "'" : "";
            html += ">";
            if (settings.isTreeView) {
                html += level ? "" + (level > 0 ? "|—" : "") + "" : "";
            }
            html += content && content != "undefined" ? content : "";
            html += "</td>";
            return html;
        }

        // 显示指定页码的数据
        self.show = function (index, type) {
            self.selected = [];
            if (!settings.apiUrl) {
                return;
            }

            if (!type || type.toLowerCase() != "post") {
                $.get(settings.apiUrl, getAjaxData(), function (data) {
                    var source = [];
                    if (settings.convertSource && $.isFunction(settings.convertSource)) {
                        source = settings.convertSource(data);
                    } else {
                        source = data;
                    }
                    addRowsToTbody(source);
                });
            } else {
                $.post(settings.apiUrl, getAjaxData(), function (data) {
                    var source = [];
                    if (settings.convertSource && $.isFunction(settings.convertSource)) {
                        source = settings.convertSource(data);
                    } else {
                        source = data;
                    }
                    addRowsToTbody(source);
                });
            }
        };

        // 获取选中的ID
        self.getSelectedId = function () {
            if (self.selected.length == 0) {
                return null;
            } else {
                return self.selected[0];
            }
        };

        self.getSelectedIds = function () {
            return self.selected;
        };

        self.clear = function () {
            self.selected = [];
            $("tr", "#myList" + rand).removeClass("selected");
            $(":checkbox", "#myList" + rand).prop("checked", false);
        };

        // 为table内的元素绑定事件
        tbody.on("click", "tr", function () {
            if ($(this).hasClass("empty")) return;
            $("tr", tbody).removeClass("selected").find(":checkbox").prop("checked", false);
            $(this).addClass("selected").find(":checkbox").prop("checked", true);
            $("#cbAll" + rand).prop("checked", false);
            self.selected = [$(this).data("value")];
        });

        // 绑定复选框点击事件
        if (settings.isMulti) {
            cbAll = $("#cbAll" + rand);

            cbAll.on("click", function () {
                if (!checkboxes) checkboxes = $(":checkbox", tbody);

                if ($(this).prop("checked")) {
                    self.selected = [];

                    $.each(checkboxes, function (idx, item) {
                        $(this).prop("checked", true);
                        self.selected.push($(this).val());

                        var tr = $("#tr" + $(item).val());
                        if (!tr.hasClass("selected")) tr.addClass("selected");
                    });
                } else {
                    checkboxes.prop("checked", false);
                    $("tr", tbody).removeClass("selected");
                    self.selected = [];
                }
            });

            tbody.on("click", ":checkbox", function (event) {
                event.stopPropagation();

                if ($(this).prop("checked")) {
                    $("#tr" + $(this).val()).addClass("selected");
                } else {
                    $("#tr" + $(this).val()).removeClass("selected");
                }
                if (!checkboxes) {
                    checkboxes = $(":checkbox", tbody);
                }

                self.selected = [];

                var unCheckedCount = 0;

                $.each(checkboxes, function () {
                    if ($(this).prop("checked")) {
                        self.selected.push($(this).val());
                    } else {
                        unCheckedCount++;
                    }
                });

                if (unCheckedCount > 0) {
                    cbAll.prop("checked", false);
                } else {
                    cbAll.prop("checked", true);
                }
            });
        }

        // 返回渲染函数的程序体
        function renderFunString() {
            var funString = "var self = this; var html = ''; $.each(data, function(idx, item) { var val = ";
            funString += (settings.valueColumn ? "item." + settings.valueColumn : "idx");
            funString += "; html += '<tr id=\"tr' + val + '\" data-value=\"' + val + '\">'; html += self.setFirstCol(val);";
            var level = null;
            if (settings.levelColumn) {
                level = settings.levelColumn;
            }
            $.each(settings.columns, function (idx, item) {
                if (item.func) {
                    funString += " html += self.setCol( " + item.func + "(item), " + item.width + ", '" + item.cssClass + "'" + (level ? " , item." + level : "") + " );";
                } else {
                    funString += " html += self.setCol( item." + item.column + ", " + item.width + ", '" + item.cssClass + "'" + (level ? " , item." + level : "") + " );";
                }
            });
            funString += " html += self.setLastCol(); html += '</tr>'; idx++; }); return html;";
            return funString;
        }

        // 将数据生成html,并插入到tbody中
        function addRowsToTbody(data) {
            if (data && data.body && data.body.length > 0) {
                var html = self.fun(data.body);
                tbody.html(html);

                if (isPager) {
                    setPagerButton(pageSize, pageindex, data.totalCount, new Date().getTime());
                }
            } else {
                var colCount = settings.columns.length + 2;
                tbody.html("<tr class='empty'><td colspan='" + colCount + "'>请求的数据为空</td></tr>");
            }
        }

        // 创建table
        function addChildrenToTable() {
            var body = "<thead>"
            if (settings.columns) {
                if (settings.isMulti) {
                    body += "<th class='chk'><input type='checkbox' id='cbAll" + rand + "' /></th>";
                } else {
                    body += "<th class='no'></th>";
                }

                $.each(settings.columns, function (idx, col) {
                    body += "<th>" + col.title + "</th>";
                });
                body += "<th></th>";
                body += "</thead><tbody id='tbody" + rand + "'></tbody>";

                table.append($(body));
            }
        }

        // 绑定分页按钮的点击事件
        function setPagerButtonEvent() {
            firstButton = $("#first" + rand);
            prevButton = $("#prev" + rand);
            nextButton = $("#next" + rand);
            lastButton = $("#last" + rand);
            currentSpan = $("#index" + rand);

            pageSizeSpan = $("#size" + rand);;
            totalCountSpan = $("#total" + rand);;
            totalPagesSpan = $("#page" + rand);;
            loadTimeSpan = $("#time" + rand);;

            firstButton.on("click", function () {
                if (!$(this).hasClass("disabled")) {
                    pageindex = 1;
                    self.show();
                }
            });

            prevButton.on("click", function () {
                if (!$(this).hasClass("disabled")) {
                    pageindex -= 1;
                    pageindex = pageindex <= 0 ? 1 : pageindex;
                    self.show();
                }
            });

            nextButton.on("click", function () {
                if (!$(this).hasClass("disabled")) {
                    pageindex += 1;
                    self.show();
                }
            });

            lastButton.on("click", function () {
                if (!$(this).hasClass("disabled")) {
                    pageindex = totalPage;
                    self.show();
                }
            });

            currentSpan.on("change", function () {
                var nc = Number($(this).val());
                if (nc && nc <= totalPage && nc > 0) {
                    pageindex = nc;
                    self.show();
                } else {
                    $(this).val(pageindex);
                }
            });
        }

        // 配置 Pager 按钮
        function setPagerButton(size, index, total, start) {
            if (total == 0) {
                pager.hide();
            } else {
                pager.show();
            }

            // 总页数
            var pages = Math.ceil(total / size);
            pages = (pages == 0 ? 1 : pages);
            totalPage = pages;

            if (pages == 1) {
                if (!firstButton.hasClass("disabled")) {
                    firstButton.addClass("disabled");
                }
                if (!prevButton.hasClass("disabled")) {
                    prevButton.addClass("disabled");
                }
                if (!nextButton.hasClass("disabled")) {
                    nextButton.addClass("disabled");
                }
                if (!lastButton.hasClass("disabled")) {
                    lastButton.addClass("disabled");
                }
            } else {
                if (index == 1) {
                    if (!firstButton.hasClass("disabled")) {
                        firstButton.addClass("disabled");
                    }
                    if (!prevButton.hasClass("disabled")) {
                        prevButton.addClass("disabled");
                    }
                } else {
                    if (firstButton.hasClass("disabled")) {
                        firstButton.removeClass("disabled");
                    }
                    if (prevButton.hasClass("disabled")) {
                        prevButton.removeClass("disabled");
                    }
                }
                currentSpan.val(index);
                if (index == pages) {
                    if (!nextButton.hasClass("disabled")) {
                        nextButton.addClass("disabled");
                    }
                    if (!lastButton.hasClass("disabled")) {
                        lastButton.addClass("disabled");
                    }
                } else {
                    if (nextButton.hasClass("disabled")) {
                        nextButton.removeClass("disabled");
                    }
                    if (lastButton.hasClass("disabled")) {
                        lastButton.removeClass("disabled");
                    }
                }
            }
            totalCountSpan.text(total);
            pageSizeSpan.text(this.pageSize);
            totalPagesSpan.text(pages);
            loadTimeSpan.text((new Date().getTime() - start));
        }

        // 获取ajax的查询参数
        function getAjaxData() {
            var param;
            if ($.isFunction(settings.getSearchData)) {
                param = settings.getSearchData();
            }
            if (isPager) {
                if ($.isArray(param)) {                                             // $("form").serializationArray()
                    param.push({ "name": "pageSize", "value": pageSize });
                    param.push({ "name": "pageIndex", "value": pageindex });
                } else if ($.isPlainObject(param)) {                                 // 自定义查询对象
                    $.extend(true, param, { "pageSize": pageSize, "pageIndex": pageindex });
                } else {                                                             // $("form").serialization()
                    param = (param ? param + "&" : "") + "pageSize=" + pageSize + "&pageIndex=" + pageindex;
                }
            }
            return !!param ? param : {};
        }

        return self;
    }

})(jQuery);

/*
 *使用范例:
 *==========================数据格式============================
 {
  "code": 200,
  "describe": "",
  "totalCount": 3,
  "body": [
    {
      "no": 1,
      "name": "王五",
      "family": {
        "father": "王老五",
        "mother": "陈静蓉"
      }
    },
    {
      "no": 2,
      "name": "张三",
      "family": {
        "father": "张作霖",
        "mother": "李培芳"
      }
    },
    {
      "no": 3,
      "name": "李四",
      "family": {
        "father": "李宗仁",
        "mother": "江少芬"
      }
    }
  ]
 }

 *==========================页面调用============================
    <script src="jquery-1.10.2.js"></script>
    <script src="myGrid.js"></script>
    <script>
        var list = $("#list").myGrid({
            apiUrl: "data.json",
            isMulti: false,
            isTree: true,
            cols: [
                { col: "no", width: 120, title: "编号", cssClass: "chk", level: 0 },
                { col: "name", width: 120, title: "姓名", level: 0 },
                { col: "family.father", width: 120, title: "父亲", level: 1, func: "addFix" }
            ],
            valueCol: "no",
            pager: "pager",
            pageSize: 2,
            cssClass: "default-list-table",
            convertSource: function(data) {
                return data;
            }
        });

        // 这个是用来转换数据的方法
        function addFix(obj) {
            return "000" + obj;
        }
        list.show();
    </script>

下拉菜单的体现和隐形效果是哪些完结的?下拉菜单本质是哪些事物?

下拉菜单是透过
transition
来完毕联网动画的。
下拉菜单 el-select-menu 本质上就是一个 div 容器而已。

  <div
    class="el-select-dropdown el-popper"
    :class="[{ 'is-multiple': $parent.multiple }, popperClass]"
    :style="{ minWidth: minWidth }">
    <slot></slot>
  </div>

别的,在代码中平时出现的打招呼下拉菜单突显和隐形的播音在 el-select-menu
mounted 方法中接受拔取:

    mounted() {
      this.referenceElm = this.$parent.$refs.reference.$el;
      this.$parent.popperElm = this.popperElm = this.$el;
      this.$on('updatePopper', () => {
        if (this.$parent.visible) this.updatePopper();
      });
      this.$on('destroyPopper', this.destroyPopper);
    }

图片 1

何以落实基本单选功用?

分析下基本功用:点击 input,彰显下拉菜单;鼠标选中一项
option,隐藏下拉菜单;input 中显得选中的结果。
为此那边看下彰显内容的 input 都有些什么风浪:

      @focus="handleFocus" // 处理 焦点
      @blur="handleBlur" // 处理 焦点 离开
      @keyup.native="debouncedOnInputChange"
      @keydown.native.down.stop.prevent="navigateOptions('next')" // 向下按键,移动到下一个 option
      @keydown.native.up.stop.prevent="navigateOptions('prev')" // 向上按键,移动到上一个 option
      @keydown.native.enter.prevent="selectOption" // 回车按键,选中option
      @keydown.native.esc.stop.prevent="visible = false"  // esc按键,隐藏下拉框
      @keydown.native.tab="visible = false" // tab按键,跳转到下一个文本框,隐藏下拉框
      @paste.native="debouncedOnInputChange" // 
      @mouseenter.native="inputHovering = true" // mouse enter 事件
      @mouseleave.native="inputHovering = false" // mouse leave 事件

从上边的这几个事件中得以了解:选中方法为
selectOption(从英文字面意思都能明了~);突显下拉框通过 visible
属性控制;以及任何按键的部分意义。那里主要首要看看 selectOption 方法。

      selectOption() {
        if (!this.visible) {
          this.toggleMenu();
        } else {
          if (this.options[this.hoverIndex]) {
            this.handleOptionSelect(this.options[this.hoverIndex]);
          }
        }
      },

逻辑就是,假诺下拉框未出示则实施 toggleMenu
方法触发下拉框,即使已突显下拉框则处理选用 option 的长河。看看那么些
toggleMenu 方法:

      toggleMenu() {
        if (!this.selectDisabled) {
          this.visible = !this.visible;
          if (this.visible) {
            (this.$refs.input || this.$refs.reference).focus();
          }
        }
      },

实在就是决定下拉菜单的来得和潜伏。如若展现的时候变焦在 input
reference 上,它们其实就是单选和多选的 input 框(多选 input 定义了
ref="input" 单选 input 定义了 ref="reference")。
从那之后,下拉菜单的显示与隐藏解决了。然后大家去找 option 点击事件:

      // 处理选项选中事件
      handleOptionSelect(option) {
        if (this.multiple) {
          // 多选
          const value = this.value.slice();
          const optionIndex = this.getValueIndex(value, option.value);
          if (optionIndex > -1) {
            // 已选中,从数组中移除
            value.splice(optionIndex, 1);
          } else if (this.multipleLimit <= 0 || value.length < this.multipleLimit) {
            // 未选中,传入数组
            value.push(option.value);
          }
          this.$emit('input', value);
          this.emitChange(value);
          if (option.created) {
            this.query = '';
            this.handleQueryChange('');
            this.inputLength = 20;
          }
          // 查询
          if (this.filterable) this.$refs.input.focus();
        } else {
          // 单选
          this.$emit('input', option.value);
          this.emitChange(option.value);
          this.visible = false;
        }
        // 渲染完成后
        this.$nextTick(() => {
          this.scrollToOption(option);
          this.setSoftFocus();
        });
      },

处理选中事件考虑了单选和多选三种情况。
如若是多选,检索选中 option 是还是不是在 value 数组中,有则移除、无则添加到
value 数组中。然后 $emit 触发 input 事件,执行 emitChange
方法。如果 option 的 created 为 true,则清空查询内容。
如若是单选,$emit 触发 input 事件将入选值传递给父组件,执行
emitChange 方法,最后隐藏下拉菜单。
最后采纳 $nextTick 方法处理下界面。
到此处,选中 option 后下拉菜单消失问题一挥而就,只剩下浮现结果到 input
中了。那个浮现结果的经过是透过对 visible
属性的监听来形成的(一方始认为在 emitChange
结果发现那只是接触改变事件的)。

      visible(val) {
        // 在下拉菜单隐藏时
        if (!val) {
          // 处理图标
          this.handleIconHide();
          // 广播下拉菜单销毁事件
          this.broadcast('ElSelectDropdown', 'destroyPopper');
          // 取消焦点
          if (this.$refs.input) {
            this.$refs.input.blur();
          }
          // 重置过程
          this.query = '';
          this.previousQuery = null;
          this.selectedLabel = '';
          this.inputLength = 20;
          this.resetHoverIndex();
          this.$nextTick(() => {
            if (this.$refs.input &&
              this.$refs.input.value === '' &&
              this.selected.length === 0) {
              this.currentPlaceholder = this.cachedPlaceHolder;
            }
          });
          // 如果不是多选,进行赋值现在 input 中
          if (!this.multiple) {
            // selected 为当前选中的 option
            if (this.selected) {
              if (this.filterable && this.allowCreate &&
                this.createdSelected && this.createdOption) {
                this.selectedLabel = this.createdLabel;
              } else {
                this.selectedLabel = this.selected.currentLabel;
              }
              // 查询结果
              if (this.filterable) this.query = this.selectedLabel;
            }
          }
        } else {
          // 下拉菜单显示
          // 处理图片显示
          this.handleIconShow();
          // 广播下拉菜单更新事件
          this.broadcast('ElSelectDropdown', 'updatePopper');
          // 处理查询事件
          if (this.filterable) {
            this.query = this.remote ? '' : this.selectedLabel;
            this.handleQueryChange(this.query);
            if (this.multiple) {
              this.$refs.input.focus();
            } else {
              if (!this.remote) {
                this.broadcast('ElOption', 'queryChange', '');
                this.broadcast('ElOptionGroup', 'queryChange');
              }
              this.broadcast('ElInput', 'inputSelect');
            }
          }
        }
        // 触发 visible-change 事件
        this.$emit('visible-change', val);
      },

从 template 中可知,展现结果的 input 绑定的 v-model
selectedLabel,而 select
是经过得到下拉菜单的显得与潜伏事件来实施结果展现部分的作用的。末了
selectedLabel 获得到了入选的 option 的 label 内容。
这样,从 点击-单选-显示 的流程就落实了。仍然很简短的。

接触mvc不久,突然没有了viewstate和服务端控件四处都觉着不顺手,很多在webform时不要求考虑的问题都冒出在前头,这其中分页时查询条件有限支撑的题目又是最让我高烧的事务,权衡再三,决定用ajax局地刷新列表的点子来解决这一个题材。网上基于jquery的grid组件很多,jquerygrid,jqgrid等等,分别试用了一下,功用实在尤其强大,但感到上有点重,配置项太多,用起来仍旧觉得束手束脚,所以想来想去,依旧用最笨的艺术自己做了一个零件,很简陋,唯一的便宜就是灵活,不难修改和操纵。

清空按钮展现和点击事件呢?

在体现结果的 input 文本框中有一个 <i> 标签,用于浮现图标。

      <!-- 用户显示清空和向下箭头 -->
      <i slot="suffix"
       :class="['el-select__caret', 'el-input__icon', 'el-icon-' + iconClass]"
       @click="handleIconClick"
      ></i>

说到底 input 左侧显示怎么图标由 iconClass 决定,其中 circle-close
就是圈子查查,即清空按钮~

      iconClass() {
        let criteria = this.clearable &&
          !this.selectDisabled &&
          this.inputHovering &&
          !this.multiple &&
          this.value !== undefined &&
          this.value !== '';
        return criteria ? 'circle-close is-show-close' : (this.remote && this.filterable ? '' : 'arrow-up');
      },

handleIconClick 方法:

      // 处理图标点击事件(删除按钮)
      handleIconClick(event) {
        if (this.iconClass.indexOf('circle-close') > -1) {
          this.deleteSelected(event);
        }
      },
      // 删除选中
      deleteSelected(event) {
        event.stopPropagation();
        this.$emit('input', '');
        this.emitChange('');
        this.visible = false;
        this.$emit('clear');
      },

终极,清空只是将文本清空掉并且关闭下拉菜单。其实当再一次打开 select
的时候,option 依然入选在事先入选的充足地方,即 HoverIndex 没有成为
-1,不知晓算不算 bug。

图片 2

option 的自定义模板是如何落到实处的?

很简短,使用了 slot 插槽。并且在 slot 中定义了默许突显方式。

    <slot>
      {{ currentLabel }}
    </slot>
  1. 指定url,协理post或get形式加载数据
  2. 自定义数据查询条件
  3. 列定义时可以对数码项举办简易的转换处理。如:数据源[{ “name”:
    “张三”,  “age”: 12, “sex”: 1 }],渲染时希望把sex的值 1 彰显为 男性
  4. 协助多选
  5. 协助分页
  6. 点击行时自动选中,多选时,点击checkbox举行选中和注销选中
  7. 可以回到选中项目标值,多选时再次回到选中值的数组

哪些促成多选,多选选中后 option 右边的勾以及 input 中的 tag 怎么样浮现?

至于多选,在刚刚讲单选的时候提及了一些了。所以有些代码就不贴出浪费篇幅了。具体逻辑如下:
先点击 input 执行 selectOption 方法显示下拉菜单,然后点击下拉菜单中的
option,执行 handleOptionSelect 方法将 option 的值都传给 value
数组。此时 value 数组改变,触发 watch 中的 value 变化监听方法。

      value(val) {
        // 多选
        if (this.multiple) {
          this.resetInputHeight();
          if (val.length > 0 || (this.$refs.input && this.query !== '')) {
            this.currentPlaceholder = '';
          } else {
            this.currentPlaceholder = this.cachedPlaceHolder;
          }
          if (this.filterable && !this.reserveKeyword) {
            this.query = '';
            this.handleQueryChange(this.query);
          }
        }
        this.setSelected();
        // 非多选查询
        if (this.filterable && !this.multiple) {
          this.inputLength = 20;
        }
      },

以上代码关键是执行了 setSelected 方法:

      // 设置选择项
      setSelected() {
        // 单选
        if (!this.multiple) {
          let option = this.getOption(this.value);
          // created 是指创建出来的 option,这里指 allow-create 创建的 option 项
          if (option.created) {
            this.createdLabel = option.currentLabel;
            this.createdSelected = true;
          } else {
            this.createdSelected = false;
          }
          this.selectedLabel = option.currentLabel;
          this.selected = option;
          if (this.filterable) this.query = this.selectedLabel;
          return;
        }
        // 遍历获取 option
        let result = [];
        if (Array.isArray(this.value)) {
          this.value.forEach(value => {
            result.push(this.getOption(value));
          });
        }
        // 赋值
        this.selected = result;
        this.$nextTick(() => {
          // 重置 input 高度
          this.resetInputHeight();
        });
      },

可以见到如若是多选,那么将 value 数组遍历,获取相应的 option
值,传给 selected。而多选界面其实就是对此那些 selected 的 v-for
遍历展现。彰显的竹签使用的是 element 的其余一个零件
el-tag

        <el-tag
          v-for="item in selected"
          :key="getValueKey(item)">
          {{ item.currentLabel }}
        </el-tag>

此处顺便提一句: option 的 created 参数用于标识是 select
组件中开创的分外用于创设条目标 option。而从 slot 插槽传入的 option
是并非传 created 参数的。

一个改动的操作:

一体化结构

以下是 select 的 template 结构,已去掉了一局地代码便于查看全体结构:

<template>
  <div>
    <!-- 多选 -->
    <div
      v-if="multiple"
      ref="tags">
      <!-- collapse tags 多选时是否将选中值按文字的形式展示 -->

        <el-tag
          type="info"
          disable-transitions>
          {{ selected[0].currentLabel }}
        </el-tag>
        <el-tag
          v-if="selected.length > 1"
          type="info"
          disable-transitions>
          + {{ selected.length - 1 }}
        </el-tag>

      <!-- 多选,多个 el-tag 组成 -->
      <transition-group @after-leave="resetInputHeight" v-if="!collapseTags">
        <el-tag
          v-for="item in selected"
          :key="getValueKey(item)"
          type="info"
          disable-transitions>
          {{ item.currentLabel }}
        </el-tag>
      </transition-group>
      <!-- 可输入文本的查询框 -->
      <input
        v-model="query"
        v-if="filterable"
        ref="input">
    </div>
    <!-- 显示结果框 read-only -->
    <el-input
      ref="reference"
      v-model="selectedLabel">
      <!-- 用户显示清空和向下箭头 -->
      <i slot="suffix"></i>
    </el-input>
    <!-- 下拉菜单 -->
    <transition>
      <el-select-menu
        ref="popper"
        v-show="visible && emptyText !== false">
        <el-scrollbar
          tag="ul"
          wrap-class="el-select-dropdown__wrap"
          view-class="el-select-dropdown__list"
          ref="scrollbar"
          v-show="options.length > 0 && !loading">
          <!-- 默认项(创建条目) -->
          <el-option
            :value="query"
            created
            v-if="showNewOption">
          </el-option>
          <!-- 插槽,用于放 option 和 option-group -->
          <slot></slot>
        </el-scrollbar>
        <!-- loading 加载中文本 -->
        <p
          v-if="emptyText &&
            (!allowCreate || loading || (allowCreate && options.length === 0 ))">
          {{ emptyText }}
        </p>
      </el-select-menu>
    </transition>
  </div>
</template>

实际都写在诠释中了~从地点内容中能够看来,select
考虑了好多气象,如单选、多选、搜索、下拉框、图标等等。并且利用 slot
插槽来收获开发者传递的 option 和 option-group 组件。
可以窥见在 select 中选择了八个外表组件,也就是说 el-select
是由三个零件组装成的一个犬牙相制组件~

  // components
  import ElInput from 'element-ui/packages/input';
  import ElSelectMenu from './select-dropdown.vue';
  import ElOption from './option.vue';
  import ElTag from 'element-ui/packages/tag';
  import ElScrollbar from 'element-ui/packages/scrollbar';

没图没本质,先来个截图看看:

最后

率先次尝试用问题取代大旨来写博客,那样望着主导是否更明确一些?
最后,说下看完 select 组件的感想:

  • element
    通过自定义的播放方法举行父子组件间的通讯。(好像以前Vue也有那么些功效,后来弃用了。)
  • 再繁杂的零部件都是由一个个基础的机件拼起来的。
  • select 作用依然挺复杂的,加上子组件 1000+
    行代码了。本文只是讲了基本功能的兑现,值得长远学习。
  • 学学了权威写组件的措施和写法~之后在融洽写组件的时候能够参见。
  • 格局、参数命名卓殊专业,一眼就能看懂具体用法。
  • 知道了
    Array.some()
    方法~

可以吗,说好了一天写出来,结果断断续续花了八日才成功。有点高估自己能力啊~
说下之后的Vue实验室博客布署:陈设再找五个复杂的 element
组件来学习,最终写一篇总计博客。然后试着祥和去创制多少个 UI
组件,学以致用。

select
采纳器是个比较复杂的零件了,通过分裂的配备可以有多种用法。有须求单独学习深造。

创设条目如何完毕?

上文中关系过,就是在 select 中默许藏了一条 option,当成立条目时突显那一个option 并显示创设内容。点击这一个 option
就足以把创设的始末添加到展现结果的 input 上了。

相关文章