Vue.js自定义下拉列表,如何实现在下拉列表区域外点击即可关闭下拉列表的功能
2017-05-30 11:11
1421 查看
在开发过程中,为了效果好看,往往需要自己开发一个下拉列表,而不是使用 HTML 自身的 select 下拉列表。然而当编写自定义下拉列表的时候,就会碰到一个问题:如果用户在下拉列表的范围外进行鼠标点击的操作,如何关闭已经打开的下拉列表?
解决思路如下:在 DOM 的根节点上添加一个 click 事件,同时下拉列表内阻止事件的默认行为和冒泡。当响应这个点击事件的时候,说明是在下拉列表范围外的点击(因为下拉列表内阻止了事件的冒泡),就可以关闭已经打开的下拉列表。
如果是纯 JS 代码,有人可能会使用
Vue.js 使用组件化的方式组织代码,会有一个根组件,可以在这个根组件上加上
那么如何让各个子组件响应根组件上的点击事件呢?可以使用Vuex来做到这一点。在这里 Vuex 起到了组件之间互相传递信息的作用。
读者可以在这个网址下载我编写的 Demo 项目:http://download.csdn.net/detail/zhangchao19890805/9855750。
推荐读者使用
下面说一下关键代码:
程序入口 main.js:
根节点 App.vue,添加了点击事件。
Vuex 文件结构
actions.js
getters.js
index.js
mutations.js
store.js
页面代码 test.vue
解决思路如下:在 DOM 的根节点上添加一个 click 事件,同时下拉列表内阻止事件的默认行为和冒泡。当响应这个点击事件的时候,说明是在下拉列表范围外的点击(因为下拉列表内阻止了事件的冒泡),就可以关闭已经打开的下拉列表。
如果是纯 JS 代码,有人可能会使用
document.onclick来添加根节点事件。不过,我现在使用 Vue.js,会选择使用 Vue.js 的方式处理这个问题。
Vue.js 使用组件化的方式组织代码,会有一个根组件,可以在这个根组件上加上
@click事件,来响应区域外的点击事件。在一个完整的应用中,可能有多种场景需要这种区域外点击关闭的功能。除了最普通的表单里的下拉列表外,还可能是网站右上角的消息提示框,或者菜单。比较合适的做法是把点击事件的具体处理逻辑放到各个组件中去。
那么如何让各个子组件响应根组件上的点击事件呢?可以使用Vuex来做到这一点。在这里 Vuex 起到了组件之间互相传递信息的作用。
读者可以在这个网址下载我编写的 Demo 项目:http://download.csdn.net/detail/zhangchao19890805/9855750。
推荐读者使用
yarn install安装所需的依赖。
下面说一下关键代码:
程序入口 main.js:
import Vue from 'vue' import App from './App.vue' import VueRouter from 'vue-router' import routes from './router' import VueSuperagent from 'vue-superagent' import Vuex from 'vuex' import 'babel-polyfill'; import store from './vuex/store'; Vue.use(VueRouter); Vue.use(VueSuperagent); Vue.use(Vuex); const router = new VueRouter({ mode: 'history', routes }) new Vue({ el: '#app', router, store, render: h => h(App) })
根节点 App.vue,添加了点击事件。
<template> <div @click="clickRoot"> <router-view></router-view> </div> </template> <script> export default { methods:{ clickRoot(event){ this.$store.dispatch("clickRootNumAction", 1); } } } </script>
Vuex 文件结构
vuex │ └─modules ├─clickRoot │ ├─actions.js │ ├─getters.js │ ├─index.js │ └─mutations.js │ └─store.js
actions.js
export default { // action 允许异步加载,实际项目中 // 这里可以发起个请求,再返回。 clickRootNumAction(context, value) { context.commit('clickRootNum', value); } }
getters.js
export default { getClickRootNum(state) { return state.clickRootNum; } }
index.js
import actions from './actions' import getters from './getters' import mutations from './mutations' const state = { clickRootNum: 0 } export default { state, actions, getters, mutations }
mutations.js
export default { clickRootNum(state, value) { let sum = state.clickRootNum + value state.clickRootNum = sum; } }
store.js
import Vue from 'vue'; import Vuex from 'vuex'; import clickRoot from './modules/clickRoot' Vue.use(Vuex); const debug = process.env.NODE_ENV !== 'production'; export default new Vuex.Store({ modules: { clickRoot }, strict: debug })
页面代码 test.vue
<template> <div > <p>测试</p> <table> <tbody> <tr> <td style="vertical-align: top;"> <div class="dropDownList"> <button class="controll" @click.prevent.stop="listShow()" @keydown.prevent.40="arrowDown1" @keydown.prevent.38="arrowUp1"> {{selectItem}} <span :class="['triangle',showList==false?'triangleShow':'triangleHidden']"></span> </button> <ul class="showList" v-if="showList" @click.prevent.stop> <input v-model="filterText" class="search"/> <li v-for="item in newObj" class="optionArea" @click="selectOption(item)"> {{item.type}} </li> </ul> </div> </td> <td style="vertical-align: top;"> <div class="dropDownList"> <button class="controll" @click.prevent.stop="listShow2()" @keydown.prevent.40="arrowDown2" @keydown.prevent.38="arrowUp2"> {{selectItem2}} <span :class="['triangle',showList2==false?'triangleShow':'triangleHidden']"></span> </button> <ul class="showList" v-if="showList2" @click.prevent.stop> <input v-model="filterText2" class="search"/> <li v-for="item in newObj2" class="optionArea" @click="selectOption2(item)"> {{item.type}} </li> </ul> </div> </td> </tr> </tbody> </table> </div> </template> <script> export default { data(){ return { showList:false, obj:[ {type:"男装"}, {type:"女装"}, {type:"童装"}, {type:"老年装"}, ], filterText:"", selectItem:"请选择", showList2:false, obj2:[ {type:"奔驰"}, {type:"桑塔纳"}, {type:"大众"}, {type:"比亚迪"}, ], filterText2:"", selectItem2:"请选择" }; }, methods:{ listShow(){ this.showList=!this.showList; if (this.showList2) { this.showList2 = false; } }, selectOption(item){ this.selectItem=item.type; this.showList=false; }, // 第一个下拉列表 按键:向下的箭头 arrowDown1(e){ if (!this.showList) { this.showList = true; } if (this.showList2) { this.showList2 = false; } }, // 第一个下拉列表 按键:向上的箭头 arrowUp1(e){ if (this.showList) { this.showList = false; } if (this.showList2) { this.showList2 = false; } }, listShow2(){ this.showList2=!this.showList2; if (this.showList) { this.showList = false; } }, selectOption2(item){ this.selectItem2=item.type; this.showList2=false; }, // 第二个下拉列表 按键:向下的箭头 arrowDown2(e){ if (!this.showList2) { this.showList2 = true; } if (this.showList) { this.showList = false; } }, // 第一个下拉列表 按键:向上的箭头 arrowUp2(e){ if (this.showList2) { this.showList2 = false; } if (this.showList) { this.showList = false; } } }, computed:{ newObj:function(){ let self = this; return self.obj.filter(function (item) { return item.type.toLowerCase().indexOf(self.filterText.toLowerCase()) !== -1; }) }, newObj2:function(){ let self = this; return self.obj2.filter(function (item) { return item.type.toLowerCase().indexOf(self.filterText2.toLowerCase()) !== -1; }) } }, watch:{ '$store.getters.getClickRootNum': function () { if (this.showList){ this.showList = false; } if (this.showList2) { this.showList2 = false; } } } }; </script> <style lang="scss" rel="stylesheet/scss" scoped> .dropDownList{ margin-left:50px; width: 150px; .controll{ position: relative; width: 150px; border: 1px solid #E3E9EF; cursor: pointer; .triangle{ display: inline-block; position: absolute; top: 7px; right: 10px; cursor: pointer; } .triangleHidden{ border-left: 5px solid transparent; border-right: 5px solid transparent; border-bottom: 8px solid #676F7F; } .triangleShow{ border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 8px solid #676F7F; } } .showList{ margin: 0; padding: 0; border: 1px solid #E3E9EF; // padding-top: 5px; padding-bottom: 5px; margin-top: 2px; width: 145px; .search{ width: 141px; border: 1px solid #E3E9EF; } .optionArea{ list-style: none; cursor: pointer; font-size: 14px; margin-left: 5px; &:hover{ background-color: #B2CFEB; color: #fff; } } } } </style>
相关文章推荐
- Vue.js实现在下拉列表区域外点击即可关闭下拉列表的功能(自定义下拉列表)
- JS使用遮罩实现点击某区域以外时弹窗的弹出与关闭功能示例
- 如何引用jQuery实现下拉列表,点击展开,点击关闭。
- BootStrap自定义popover,点击区域隐藏功能的实现
- iOS WebView 如何通过js获取网页中所有图片并加入点击事件,实现浏览图片的功能
- 如何绑定android点击事件--跳转到另一个页面并实现关闭功能?
- vue实现点击关注后及时更新列表功能
- 如何用js实现,在一个页面有个输入文档框,然后点击查询按扭后,调用IE的查询功能在当前页面进行查询
- jQuery实现点击自身以外区域关闭弹出层功能完整示例【改进版】
- 原生js实现div点击其他地方隐藏下拉列表
- 进击的KFC:iOS WebView 如何通过js获取网页中所有图片并加入点击事件,实现浏览图片的功能
- vue实现弹框遮罩点击其他区域弹框关闭及v-if与v-show的区别介绍
- js对象如何实现数组索引功能并且自定义自己的方法 (4种方法)
- 使用自定义的item、Adapter和AsyncTask、第三方开源框架PullToRefresh联合使用实现自定义的下拉列表(从网络加载图片显示在item中的ImageView)
- 页面弹框之外的区域点击关闭弹框js
- Vue.js学习 Item16 – 实现一个自定义分页组件vue-paginaiton
- MySql计数器,如网站点击数,如何实现高性能高并发的计数器功能
- 使用vue.js实现checkbox的全选和多个的删除功能
- Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能
- vue.js vue-router如何实现无效路由(404)的友好提示