有个需求,下拉框里的选项分组后,因为组名可能会很长,于是就想添加一个文本超出时显示省略号的样式,这个用 CSS 就能做到,小意思。不过,如果文本超出后,我还想有一个能展示当前文字所有信息的提示。因为本身就是一个浮层了,再添加一个浮层就感觉很怪(说实话也不好加 😂),于是就想用原生的 title 实现(先看效果,如下图所示)。

思路

首先可能会想到,把 OptionGroup 上加一个 title 属性,不过这会导致一个问题(以下面的代码为例):

<Select style="width:200px">
  <OptionGroup
    label="AAAAAAAAAAAAAAAAAAAAAAAA"
    title="AAAAAAAAAAAAAAAAAAAAAAAA"
  >
    <Option v-for="item in cityList1" :value="item.value" :key="item.value">
      {{ item.label }}
    </Option>
  </OptionGroup>
  <OptionGroup
    label="BBBBBBBBBBBBBBBBBBBBBBBB"
    title="BBBBBBBBBBBBBBBBBBBBBBBB"
  >
    <Option v-for="item in cityList2" :value="item.value" :key="item.value">
      {{ item.label }}
    </Option>
  </OptionGroup>
</Select>

首先要清楚 title 生效的范围,给元素加上这个属性后,只要是在这个元素的区域内,鼠标移入都会有这个 title 的提示。所以,鼠标在移入每个 Option 上时,出现这个提示,也没毛病。不过,这给用户就会有误导,明明 Option 中的文本是这个,提示出来的却是另一个。所以,这个方法有瑕疵,不可取 🙁。

先想一想上面的问题,归根结底是没有把 title 加到真正的组名上。翻了翻 OptionGroup 的内部实现,也没有关于 label 的插槽什么的,就是单纯的一个 div。显然,通过组件自身提供的一些属性已经不能达到我们的目的了。我们需要找到那个组名对应的 DOM 节点,然后给这个节点添加一个 title 的属性。给 DOM 节点设置属性,那就需要用到一个方法: Element.setAttribute()

实现

通过上面的分析,我们需要得到组名对应的那个 DOM 节点,在 Vue 中操作原生 DOM ,你是不是想到了指令。指令的生命周期方法中的第一个参数(el)就是当前的 DOM 节点。当然,这个 el 不一定就是组名的 DOM 节点(在控制台打印一下就能知道了),如果不是,那组名的 DOM 一定是 el 的子元素,所以我们需要在 el 中查找组名的 DOM 节点,在当前节点下查找后代节点,就需要用到另一个方法: Element.querySelector()

打开控制台,查看一下组名的 DOM 节点的 classivu-select-group-title),然后我们只需要按照下面的代码,就能给组名的 DOM 添加一个 title 属性了。

el.querySelector(".ivu-select-group-title").setAttribute("title", "AAA")

归纳一下,把这个指令做得更通用一点,把那些容易变化的部分做成变量,在使用指令时传入。这里只有 classtitle 需要外部传入。title 可以写在指令绑定的值中,而 class 可以用指令的 arg 传入。也即:v-set-title-attr:ivu-select-group-title="title"

代码

最后的整体代码,很简短。

export default {
  directives: {
    setTitleAttr: {
      bind(el, binding, vnode) {
        const { value, arg } = binding
        // 判断是否传入了 arg
        if (arg) {
          el = el.querySelector(`.${arg}`)
        }
        el.setAttribute("title", value)
      }
    }
  }
}

使用:

<Select style="width:200px">
  <OptionGroup
    label="AAAAAAAAAAAAAAAAAAAAAAAA"
    v-set-title-attr:ivu-select-group-title="'AAAAAAAAAAAAAAAAAAAAAAAA'"
  >
    <Option v-for="item in cityList1" :value="item.value" :key="item.value">
      {{ item.label }}
    </Option>
  </OptionGroup>
  <OptionGroup
    label="BBBBBBBBBBBBBBBBBBBBBBBB"
    v-set-title-attr:ivu-select-group-title="'BBBBBBBBBBBBBBBBBBBBBBBB'"
  >
    <Option v-for="item in cityList2" :value="item.value" :key="item.value">
      {{ item.label }}
    </Option>
  </OptionGroup>
</Select>