0

Think i'm cracking up, this is some very basic stuff but it doesn't seem to be working...

Basically clicking the link should toggle display between true and false, but this isn't the case.

Vue.component('dropdown', {
    props: [ 'expanded' ],
    data: function() {
      return {
        display: !!(this.expanded)
      }
    },
    template: '<div><transition name="expand"><slot :display="display"></slot></transition></div>'
  });
  window.app = new Vue({
   el: '#app'
  });
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
    <dropdown>
      <div slot-scope="{ display }">
        <a href="javascript:void(0)" @click="display = !display">Toggle {{ display }}</a>
        <div v-if="display">
          Dropdown content
        </div>
      </div>
  </dropdown>
</div>

Edit:

Updated code, I forgot I changed that, I did infact have the click event as display = !display. But even with that said, if you had tried to click the button you would see that it doesn't change the true either...

3
  • 1
    @click="display = true" just sets it to true always. @click="display = !display", perhaps? Commented Nov 30, 2018 at 19:34
  • All you need is @click="display = !display" Commented Nov 30, 2018 at 19:39
  • Per your update in the now-deleted answer, try @click.prevent. Commented Nov 30, 2018 at 19:42

2 Answers 2

4

Updating after a correcting comment from thanksd. I stumbled onto the right answer without really understanding it.

The problem is that within the slot, display refers to an item in the scope-slot object. Updating it there does not update the actual source variable. If you pass in and call a function, the proper variable is updated.

Vue.component('dropdown', {
  props: ['expanded'],
  data: function() {
    return {
      display: Boolean(this.expanded)
    }
  },
  methods: {
    toggle() {
        this.display = !this.display;
    }
  },
  template: '<div><transition name="expand"><slot :display="display" :toggle="toggle"></slot></transition></div>'
});

new Vue({
  el: '#app'
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
  <dropdown>
    <div slot-scope="{display, toggle}">
      <a href="javascript:void(0)" @click="toggle">Toggle {{ display }}</a>
      <div v-if="display">
        Dropdown content
      </div>
    </div>
  </dropdown>
</div>

Sign up to request clarification or add additional context in comments.

4 Comments

I think this is the best solution, although it's not necessary to name the scoped slot object if you use object destructuring as Martyn is doing in his example. So slot-scope="ss" could be slot-scope="{ display, toggle }". The key to your answer is that adding the toggle method and passing it via the slot-scope allows the parent scope to indirectly update the display property of the dropdown component instance. The issue with Martyn's example is that he is updating the property of a variable in the scope-slot, which is not the same as updating the related property on the instance.
Thanks, @thanksd, for clarifying that. I have updated my answer accordingly.
Ah this makes sense now thanks. I thought you could directly change the data property from within the slot as i'm sure I have seen that done somewhere before, however I may be mistaken. Only reason I wanted to affect it directly was to avoid making a method if there doesn't need to be.
If the data property were an object, you would be able to change its members and have those changes reflect in the source object (because object copies are aliases).
1

One solution would be to implement a v-model for the dropdown component which would allow you to two-way bind the display property to a property in the parent. That way you wouldn't need to pass anything via the slot-scope.

Here's an example of that:

Vue.component('dropdown', {
  props: [ 'value' ],
  data() {
    return {
      display: !!(this.value)
    }
  },
  watch: {
    value(value) {
      this.$emit('input', value);
    }
  },
  template: '<div><transition name="expand"><slot></slot></transition></div>'
});

new Vue({
  el: '#app',
  data() {
    return { dropdownToggle: false }
  }
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
    <dropdown v-model="dropdownToggle">
      <div>
        <a href="javascript:void(0)" @click="dropdownToggle = !dropdownToggle">
          Toggle {{ dropdownToggle }}
        </a>
        <div v-if="dropdownToggle">
          Dropdown content
        </div>
      </div>
  </dropdown>
</div>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.