概述

之前写vue的时候,对于下拉框,我是通过在组件内设置标记来控制是否弹出的,但是这样有一个问题,就是点击组件外部的时候,怎么也控制不了下拉框的关闭,用户体验非常差。

当时想到的解决方法是:给根实例创建一个标记来控制,然后一级一级的把这个标记传进来。但是这样每次配置都要改根组件,非常不灵活。

最近看museUI库,发现它的下拉框Select实现的非常灵活,点击组件外也能控制下拉框关闭,于是想探究一番,借此机会也深入学习一下vue。

museUI源码

首先去看Select的源码:

directives: [{
name: 'click-outside',
value: (e) => {
if (this.open && this.$refs.popover.$el.contains(e.target)) return;
this.blur();
}
}],

可以看到,有个click-outside和popover,然后它是通过用自定义指令directives实现的。然后去museUI搜popover,果然这是一个弹出组件,并且能够在组件外部控制弹窗关闭。于是开始看popover的源码:

close (reason) {
if (!this.open) return;
this.$emit('update:open', false);
this.$emit('close', reason);
},
clickOutSide (e) {
if (this.trigger && this.trigger.contains(e.target)) return;
this.close('clickOutSide');
},

可以看到,它也是通过click-outside来实现的,click-outside字面意思是点击外面,应该就是这个了。然后看click-outside的源码:

name: 'click-outside',
bind (el, binding, vnode) {
const documentHandler = function (e) {
if (!vnode.context || el.contains(e.target)) return;
if (binding.expression) {
vnode.context[el[clickoutsideContext].methodName](e);
} else {
el[clickoutsideContext].bindingFn(e);
}
};
el[clickoutsideContext] = {
documentHandler,
methodName: binding.expression,
bindingFn: binding.value
};
setTimeout(() => {
document.addEventListener('click', documentHandler);
}, 0);
},

原来它是通过自定义指令,在组件创建的时候,给document绑定一个全局click事件,当点击document的时候,通过判断点击节点来控制弹窗关闭的。这差不多就是事件代理。

所以总结一下,要实现组件外部控制组件弹窗的关闭,主要利用directives,bind,document就行了。

自己实现

既然知道原理就有点跃跃欲试了,通过查阅官方文档得知,directives可以用于局部组件,这样就变成了局部指令。于是写代码如下:

<template>
<div class="pop-over">
<a @click="toggleOpen" class="pop-button" href="javascript: void(0);">
{{ 按钮1 }}
</a>
<ul v-clickoutside="close" v-show="open" class="pop-list">
<li>选项1</li>
<li>选项2</li>
<li>选项3</li>
<li>选项4</li>
</ul>
</div>
</template> <script>
export default {
name: 'PopOver',
data() {
return {
open: false
}
},
methods: {
toggleOpen: function() {
this.open = !this.open;
},
close: function(e) {
if(this.$el.contains(e.target)) return;
this.open = false;
}
},
directives: {
clickoutside: {
bind: function (el, binding, vnode) {
const documentHandler = function (e) {
if (!vnode.context || el.contains(e.target)) return;
binding.value(e);
}; setTimeout(() => {
document.addEventListener('click', documentHandler);
}, 0);
}
}
}
}
</script>

注意,在我们close方法里面,我们通过判断点击节点是否被组件包含,如果包含的话,不执行关闭行为。

但是上面的组件不通用,正好官方文档学习了slot,于是用slot改写如下:

<template>
<div class="pop-over">
<a @click="toggleOpen" class="pop-button" href="javascript: void(0);">
{{ buttonText }}
</a>
<ul v-clickoutside="close" v-show="open" class="pop-list">
<slot></slot>
</ul>
</div>
</template> <script>
export default {
name: 'PopOver',
props: ['buttonText'],
data() {
return {
open: false
}
},
methods: {
toggleOpen: function() {
this.open = !this.open;
},
close: function(e) {
if(this.$el.contains(e.target)) return;
this.open = false;
}
},
directives: {
clickoutside: {
bind: function (el, binding, vnode) {
const documentHandler = function (e) {
if (!vnode.context || el.contains(e.target)) return;
binding.value(e);
}; setTimeout(() => {
document.addEventListener('click', documentHandler);
}, 0);
}
}
}
}
</script> <style scoped>
.pop-over {
position: relative;
width: 100%;
height: 100%;
}
.pop-button {
position: relative;
width: 100%;
height: 100%;
text-decoration:none;
color: inherit;
}
.pop-list {
position: absolute;
left: 0;
top: 0;
}
.pop-list li {
width: 100%;
height: 100%;
padding: 8px 3px;
list-style:none;
}
</style>

利用props自定义按钮文字,slot自定义弹窗文字,这样一个简易的Popover组件就完成了。

我学到了什么

  1. 对directives自定义指定,事件代理,slot练手一番,感觉很爽。
  2. 在看源码的过程中,也看到了render方法的使用,以及museUI的组件化思想。
  3. 对于组件外控制组件的行为有了新的思路。

vue实现一个简易Popover组件的更多相关文章

  1. 过年了,基于Vue做一个消息通知组件

    前言 今天除夕,在这里祝大家新年快乐!!!今天在这个特别的日子里我们做一个消息通知组件,好,我们开始行动起来吧!!!项目一览 效果很简单,就是这种的小卡片似的效果. 我们先开始写UI页面,可自定义消息 ...

  2. vue封装一个弹框组件

    这是一个提示框和对话框,例:   这是一个组件 eject.vue <template> <div class='kz-cont' v-show='showstate'> &l ...

  3. Angular2-编写一个简易的组件

    Angular2组件可以这么理解:编写一个类,然后在类的上面用组件装饰器装饰一下,这个类就成组件了. 所以编写组件分两步:1)编写类:2)编写装饰器 1)编写类: export class Simpl ...

  4. vue实现一个会员卡的组件(可以动态传入图片(分出的一个组件)、背景、文字、卡号等)

    自己在写这个组件的时候主要遇到的问题就是在动态传入背景图片或者背景色的时候没能立马顺利写出来,不过现在实现了这个简单组件就和大家分享一下 <template> <div class= ...

  5. vue小案例--简易评论区

    一.小案例(评论区) 1.流程 (1)分析静态页面.(vue项目创建参考https://www.cnblogs.com/l-y-h/p/11241503.html)(2)拆分静态页面,变成一个个组件. ...

  6. vue + socket.io实现一个简易聊天室

    vue + vuex + elementUi + socket.io实现一个简易的在线聊天室,提高自己在对vue系列在项目中应用的深度.因为学会一个库或者框架容易,但要结合项目使用一个库或框架就不是那 ...

  7. 发布自己第一个npm 组件包(基于Vue的文字跑马灯组件)

    一.前言 总结下最近工作上在移动端实现的一个跑马灯效果,最终效果如下: 印象中好像HTML标签的'marquee'的直接可以实现这个效果,不过 HTML标准中已经废弃了'marquee'标签 既然HT ...

  8. 一个vue的全局提示框组件

    <template> <!-- 全局提示框 --> <div v-show="visible" class="dialog-tips dia ...

  9. Vue 实现一个分页组件

    实现分页组件要分三个部分 样式,逻辑,和引用 首先新建一个vue文件用来承载组件内容 第一步:构建样式 <template> <nav> <ul class=" ...

随机推荐

  1. 解决Jenkins用shell脚本部署后,Jenkins自动杀掉启衍生出来的守护进程

    Jenkins部署java项目遇到的问题: 1.Jenkins执行构建后,需要手动执行startup.sh,站点才能正常访问 产生原因: shell脚本发布时,会衍生进程,Jenkins默认会自动杀掉 ...

  2. JavaScript并发模型与Event Loop (转载)

    并发模型可视化描述 model.svg 如上图所示,Javascript执行引擎的主线程运行的时候,产生堆(heap)和栈(stack),程序中代码依次进入栈中等待执行, 若执行时遇到异步方法,该异步 ...

  3. 6B - 火星A+B

    读入两个不超过25位的火星正整数A和B,计算A+B.需要注意的是:在火星上,整数不是单一进制的,第n位的进制就是第n个素数.例如:地球上的10进制数2,在火星上记为“1,0”,因为火星个位数是2进制的 ...

  4. 为什么23种设计模式没有 MVC

    MVC的是为了把数据(Model)和视图(View)分离开来,然后用控制器(Controller)来粘合M和V之间的关系. MVC是观察者模式(Observer), 策略模式(Strategy)和组合 ...

  5. 201621123002《Java程序设计》第八周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图或其他)归纳总结集合相关内容. 2. 书面作业 1. ArrayList代码分析 1.1 解释ArrayList的contains源代码 从源代码中可以看出 ...

  6. HDU 1520.Anniversary party 基础的树形dp

    Anniversary party Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others ...

  7. [ES]elasticsearch章5 ES的分词(二)

    Elasticsearch 中文搜索时遇到几个问题: 当搜索关键词如:“人民币”时,如果分词将“人民币”分成“人”,“民”,“币”三个单字,那么搜索该关键词会匹配到很多包含该单字的无关内容,但是如果将 ...

  8. laravel-重定向携带自定义消息

    在控制器写: return redirect(route('member_create'))->with('success', '操作成功'); //指定到路由名member_create // ...

  9. java中的Condition协作线程接口类

    在Java的Condition接口中,存在的几个方法跟Synchronized中的wait(),waitall(),wait(time ^),这个几个方法一一对应起来,但是在Lock.newCondi ...

  10. list.get(0)防止NPE

    package com.chuangjin.p2p; import com.chuangjin.core.utils.JsonUtils; import java.util.ArrayList; im ...