Vue js切换每个元素的类

时间:2017-08-15 16:19:48

标签: javascript vue.js vuejs2

虽然在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>

看看这里发生了什么?如果我点击这两个元素中的一个,两个元素都会同时切换类,因为它正在改变一个全局变量。现在,我如何切换的类单击的元素?

5 个答案:

答案 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}}
})

以下是它的使用方法。

&#13;
&#13;
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;
&#13;
&#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)
  },
});