虽然在Vuejs中有许多切换类的例子,但我还没有找到一个切换类缩小范围的类。如果我定义一个这样的全局变量:
data: {
toggle: false
}
当我有一个元素时会遇到问题,比如导航栏:
<ul class="menu">
<li class="has-dropdown" v-bind:class="{ 'is-open': toggle }" @click="toggle = !toggle">
Foo
<ul class="dropdown">
<li>Dropdown Item 1</li>
<li>Dropdown Item 2</li>
</ul>
</li>
<li class="has-dropdown" v-bind:class="{ 'is-open': toggle }" @click="toggle = !toggle">
Bar
<ul class="dropdown">
<li>Dropdown Item 1</li>
<li>Dropdown Item 2</li>
</ul>
</li>
</ul>
看看这里发生了什么?如果我点击这两个元素中的一个,两个元素都会同时切换类,因为它正在改变一个全局变量。现在,我如何切换的类单击的元素?
答案 0 :(得分:5)
VueJS(以及大多数其他现代Web框架)的基本指导原则是一切都来自模型。
你从不谈论操纵DOM;相反,你制作一个描述你想要的效果的模型。
在您的情况下,这意味着您需要两个data
属性,而不是一个。
但是,您实际应该做的是使每个列表项成为自己的子组件(然后将获得自己的模型)。使用插槽指定每个插槽中的不同内容。
答案 1 :(得分:3)
这只是@SLaks在他的回答中提到的一个小例子。基本上将列表元素转换为它们自己的组件,以便它们可以拥有自己的状态。
Vue.component("clicktoggle", {
template:`<li :class="{ 'is-open': toggle }" @click="toggle = !toggle"><slot></slot></li>`,
data() {return {toggle: false}}
})
以下是它的使用方法。
console.clear()
Vue.component("clicktoggle", {
template:`<li :class="{ 'is-open': toggle }" @click="toggle = !toggle"><slot></slot></li>`,
data() {return {toggle: false}}
})
new Vue({
el:"#app"
})
&#13;
.has-dropdown {
cursor: pointer;
}
.has-dropdown:not(.is-open) ul {
display: none
}
&#13;
<script src="https://unpkg.com/vue@2.2.6/dist/vue.js"></script>
<div id="app">
<ul class="menu">
<li class="has-dropdown" is="clicktoggle">
Foo
<ul class="dropdown">
<li>Dropdown Item 1</li>
<li>Dropdown Item 2</li>
</ul>
</li>
<clicktoggle class="has-dropdown">
Bar
<ul class="dropdown">
<li>Dropdown Item 1</li>
<li>Dropdown Item 2</li>
</ul>
</clicktoggle>
</ul>
</div>
&#13;
Vue让这个小组件变得非常简单。一个可能的快速增强是添加一个属性来指定你要切换的类。
答案 2 :(得分:2)
我处理这个的方式不是使用布尔值而是使用索引(数字或其他)。检查是否toggle === index
并点击设置切换为索引或-1
这是一个使用字符串值作为切换的工作版本
https://jsfiddle.net/dnqp2nc9/1/
new Vue({
el: '#app',
data: {
toggle: null
}
})
.has-dropdown li{
opacity: 0.2;
}
.is-open li{
opacity: 1;
}
<script src="https://unpkg.com/vue"></script>
<div id="app">
{{toggle}}
<ul class="menu">
<li class="has-dropdown" v-bind:class="{ 'is-open': toggle === 'foo' }" @click="toggle = toggle !== 'foo' ? 'foo' : null">
Foo [{{toggle === 'foo' ? 'open' : 'closed'}}]
<ul class="dropdown">
<li>Dropdown Item 1</li>
<li>Dropdown Item 2</li>
</ul>
</li>
<li class="has-dropdown" v-bind:class="{ 'is-open': toggle === 'bar' }" @click="toggle = toggle !== 'bar' ? 'bar' : null">
Bar [{{toggle === 'bar' ? 'open' : 'closed'}}]
<ul class="dropdown">
<li>Dropdown Item 1</li>
<li>Dropdown Item 2</li>
</ul>
</li>
</ul>
</div>
打开/关闭多个,这是一个数组版本 https://jsfiddle.net/hLm82x1d/1/
new Vue({
el: '#app',
data: {
toggle: []
},
methods: {
toggleItem: function (key) {
var i = this.toggle.indexOf(key)
if (i < 0) {
this.toggle.push(key)
} else {
this.toggle.splice(i, 1)
}
}
}
})
.has-dropdown li{
opacity: 0.2;
}
.is-open li{
opacity: 1;
}
<script src="https://unpkg.com/vue"></script>
<div id="app">
{{toggle}}
<ul class="menu">
<li class="has-dropdown" v-bind:class="{ 'is-open': toggle.indexOf('foo') >= 0 }" @click="toggleItem('foo')">
Foo [{{toggle === 'foo' ? 'open' : 'closed'}}]
<ul class="dropdown">
<li>Dropdown Item 1</li>
<li>Dropdown Item 2</li>
</ul>
</li>
<li class="has-dropdown" v-bind:class="{ 'is-open': toggle.indexOf('bar') >= 0 }" @click="toggleItem('bar')">
Bar [{{toggle === 'bar' ? 'open' : 'closed'}}]
<ul class="dropdown">
<li>Dropdown Item 1</li>
<li>Dropdown Item 2</li>
</ul>
</li>
</ul>
</div>
答案 3 :(得分:2)
令我难以置信的是,如此简单的事情需要使用现代框架的大量代码,这也是JavaScript开发变得如此复杂的原因。我使用了一个普通的JavaScript监听器来解决这个问题。
<li class="has-dropdown" @click="toggle">
...
</li>
...
methods: {
toggle: function( event ) {
event.target.classList.toggle('is-open')
}
}
...
答案 4 :(得分:0)
这似乎对我有用(Nuxt和Bootstrap 5),想法是将下拉列表作为通过数据切换的子组件,这仍在进行中,但它似乎起作用,我正在学习Js和Vue走,所以这可能不是最好的方法。
ThePrimary.vue
<template>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<nuxt-link class="navbar-brand" to="/">Navbar</nuxt-link>
<button
@click="isNavbarCollapsed = !isNavbarCollapsed"
ref="navbar-toggler"
:aria-expanded="[!isNavbarCollapsed ? 'true' : 'false']"
:class="{ collapsed: isNavbarCollapsed}"
class="navbar-toggler"
type="button"
data-toggle="collapse"
data-target="#navbarNavDropdown"
aria-controls="navbarNavDropdown"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div
:class="{ show: !isNavbarCollapsed}"
class="collapse navbar-collapse"
id="navbarNavDropdown"
>
<NavbarNav :items="loadedPrimaryMenu" />
</div>
</div>
</nav>
</template>
<script>
import NavbarNav from '@/components/Navigation/ThePrimary/NavbarNav'
export default {
name: 'TheNavigationPrimary',
data() {
return {
isNavbarCollapsed: true
}
},
computed: {
loadedPrimaryMenu() {
return this.$store.getters.loadedPrimaryMenu
}
},
components: {
NavbarNav
}
}
</script>
<style scoped lang="scss">
</style>
NavbarNav.vue
<template>
<ul class="navbar-nav">
<li
v-for="item in items"
:key="item.id"
class="nav-item"
:class="{ dropdown: hasChildren(item.children) }"
>
<NavLink
v-if="!hasChildren(item.children)"
:attributes="item"
/>
<NavbarNavDropdownMenu
v-else
:item="item"
/>
</li>
</ul>
</template>
<script>
import NavbarNavDropdownMenu from "@/components/Navigation/ThePrimary/NavbarNavDropdownMenu";
import NavLink from '@/components/Navigation/NavLink';
export default {
name: "NavbarNav",
props: {
items: {
type: Array,
required: true,
},
},
data() {
return {
};
},
methods: {
hasChildren(item) {
return item.length > 0 ? true : false;
},
},
components: {
NavbarNavDropdownMenu,
NavLink
}
};
</script>
<style scoped lang="scss">
</style>
NavbarNavDropdownMenu.vue
<template>
<span v-if="item">
<nuxt-link
to="#"
@click.prevent.native="openDropdownMenu"
v-click-outside="closeDropdownMenu"
:title="item.title"
:class="[
item.cssClasses,
{ show: isDropdownMenuVisible }
]"
:id="`navbarDropdownMenuLink-${item.id}`"
:aria-expanded="[isDropdownMenuVisible ? true : false]"
class="nav-link dropdown-toggle"
aria-current="page"
role="button"
data-toggle="dropdown"
>
{{ item.label }}
</nuxt-link>
<ul
:class="{ show: isDropdownMenuVisible }"
:aria-labelledby="`navbarDropdownMenuLink-${item.id}`"
class="dropdown-menu"
>
<li v-for="item in item.children" :key="item.id">
<NavLink
:attributes="item"
class="dropdown-item"
/>
</li>
</ul>
</span>
</template>
<script>
import NavLink from '@/components/Navigation/NavLink';
export default {
name: "DropdownMenu",
props: {
item: {
type: Object,
required: true,
},
},
data() {
return {
isDropdownMenuVisible: false,
};
},
methods: {
openDropdownMenu() {
this.isDropdownMenuVisible = !this.isDropdownMenuVisible;
},
closeDropdownMenu() {
this.isDropdownMenuVisible = false;
}
},
components: {
NavLink
}
};
</script>
<style scoped lang="scss">
</style>
NavLink.vue
<template>
<component
v-bind="linkProps(attributes.path)"
:is="attributes"
:title="attributes.title"
:class="[ attributes.cssClasses ]"
class="nav-link active"
aria-current="page"
prefetch
>
{{ attributes.label }}
</component>
</template>
<script>
export default {
name: 'NavLink',
props: {
attributes: {
type: Object,
required: true
}
},
methods: {
linkProps (path) {
if (path.match(/^(http(s)?|ftp):\/\//) || path.target === '_blank') {
return {
is: 'a',
href: path,
target: '_blank',
rel: 'noopener'
}
}
return {
is: 'nuxt-link',
to: path
}
}
}
}
</script>
<style scoped lang="scss">
</style>
要在外部单击时关闭下拉列表
import Vue from 'vue'
Vue.directive('click-outside', {
bind: function (el, binding, vnode) {
el.clickOutsideEvent = function (event) {
if (!(el == event.target || el.contains(event.target))) {
vnode.context[binding.expression](event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent)
},
unbind: function (el) {
document.body.removeEventListener('click', el.clickOutsideEvent)
},
});