physicsboy
physicsboy

Reputation: 6316

Toggle collapse different divs on click with VueJS v-on:click and Vanilla JS

Is it possible using VueJS and vanilla JS to collapse one of many divs?

I have data within separate cards featuring a title and a body - I am wanting the body of the card to collapse/expand when the title is clicked.

VueJS is being used, and I would like to keep JQuery out of it for the time being, focusing on vanilla JS.

See this fiddle for an example of what it looks like. Please bare in mind that I am FULLY AWARE of not using the same ID for multiple elements - this was a quick demo for illustration purposes.

<div class="section">
    <div class="title" @click="toggle"><span class="toggleIcon" id="toggleIcon">{{toggleIcon}}</span>Toggle This Section</div>
    <hr/>
    <div class="body" id="toggle">
        <img style="height:100px" src="https://cdn.vox-cdn.com/thumbor/Pkmq1nm3skO0-j693JTMd7RL0Zk=/0x0:2012x1341/1200x800/filters:focal(0x0:2012x1341)/cdn.vox-cdn.com/uploads/chorus_image/image/47070706/google2.0.0.jpg">
     </div>
</div>

The problem I have at the moment is that I can collapse a div, but not individual divs.

Upvotes: 4

Views: 19512

Answers (2)

craig_h
craig_h

Reputation: 32694

Remember Vue.js is data driven, so changes in the DOM should reflect underlying data changes. In your case your case you should wrap the cards up in a component, so they track their own individual state, and then use a flag with v-show to show and hide the section:

Vue Instance

Vue.component('card', {
  template: '#card',
  methods: {
    toggle() {
     this.showSection = !this.showSection
    }
  },
  data() {
    return {
      showSection: true, // Flag to show section
      imageUrl: 'https://cdn.vox-cdn.com/thumbor/Pkmq1nm3skO0-j693JTMd7RL0Zk=/0x0:2012x1341/1200x800/filters:focal(0x0:2012x1341)/cdn.vox-cdn.com/uploads/chorus_image/image/47070706/google2.0.0.jpg',
      toggleIcon: '+'
    }
  }
})

Markup

  <div class="section">
    <div class="title" v-on:click="toggle">
      <span class="toggleIcon" id="toggleIcon">{{toggleIcon}}</span> Toggle This Section
    </div>
    <hr/>
    <!-- BIND v-show to the showSection data property to reflect changes -->
    <div class="body" v-show="showSection">
      <img v-bind:src="imageUrl">
    </div>
  </div>

Note that I'm not touching the DOM, I'm just toggling the showSection flag and Vue is updating the DOM for me.

And here's the JSFiddle: https://jsfiddle.net/craig_h_411/j1wh75v9/1/

Passing different content to the cards

If you want to pass different content to each card you can use a slot:

<template id="card">
  <div class="section">
    <div class="title" v-on:click="toggle">
      <span class="toggleIcon" id="toggleIcon">{{toggleIcon}}</span> Toggle This Section
    </div>
    <hr/>
    <div class="body" v-show="showSection">
      <!-- Add slot details here -->
      <slot></slot>
    </div>
  </div>
</template>

Slots allow you to insert content into your component from the parent, as I'm only using a single slot, anything that I place between my component tags gets injected where I placed my slot tags in my component, so:

<card>I'm a card</card>
<card> Im another card</card>

In these examples, the message between the card tags will be added to the slot. Slots get compiled in the parent scope, not the scope of the component, so if you want to use data properties then they must be available in the parent, similarly you cannot access data properties and methods in the component from the slot data.

Here's the JSFiddle for that: https://jsfiddle.net/craig_h_411/ew1epg61/

Upvotes: 6

Krzysztof Baran
Krzysztof Baran

Reputation: 21

This another take on the collapsable card (with slots) similar to @craig_h:

<template>
  <div class="section">
    <div :class="[isActive ? 'active' : '', 'collapsible']" v-on:click="toggle">
      Toggle This Section <span class="toggleIcon" id="toggleIcon">{{ toggleIcon }}</span>
    </div>
    <div :class="[isActive ? 'block' : 'none', 'content']" v-show="isActive">
      <slot></slot>
    </div>
  </div>
</template>

<script>
export default {
  name: 'CollapsableCard',
  components: {},
  data() {
    return {
      isActive: false,
      toggleIcon: '+',
    }
  },
  methods: {
    toggle() {
      this.isActive = !this.isActive
    },
  },
}
</script>

<style scoped>
.collapsible {
  background-color: #d8f8ea;
  color: black;
  cursor: pointer;
  padding: 18px;
  width: 100%;
  border: none;
  text-align: left;
  outline: none;
}

.content {
  padding: 0 18px;
  overflow: hidden;
  background-color: #eefcf6;
}
</style>

Upvotes: 0

Related Questions