1

I have two components. One child component that has two slot populated by a DOM coming from another component. I use this component inside another parent component.

What I want is to close the child component when I click on an element in my parent component.

HTML

<div id="app">
  <parent></parent>
</div>

JS

// Child component.
const ChildTemplate = `
    <div class="m-dropdown">
    <button
      class="m-dropdown__button"
      aria-haspopup="true"
      aria-expanded="false"
      @click="handleClick"
      @keyup.esc="close"
      @closeDropdown="isActive = false"
    >
      <slot name="dropdownToggle"></slot>
    </button>
    <div
      class="m-dropdown__content"
      v-show="isActive"
    >
      <slot name="dropdownContent"></slot>
    </div>
  </div>
`;
const Child = {
    template: ChildTemplate,
    data() {
      return {
        isActive: false,
        dropdownCTA: null,
        dropdownCTAClassname: 'm-dropdown__button',
      };
    },
    methods: {
      handleClick(e) {
        this.isActive = !this.isActive;
        this.dropdownCTA = e.target.closest('button');
        this.dropdownCTA.setAttribute('aria-expanded', 'true');
      },
      close() {
        if (this.isActive) {
          this.isActive = false;
          this.dropdownCTA.setAttribute('aria-expanded', 'false');
        }
      },
      documentClick(e) {
        const el = this.$el;
        if (this.isActive && el !== e.target && !el.contains(e.target)) {
          this.close();
        }
      },
    },
    created() {
      document.addEventListener('click', this.documentClick);
    },
    destroyed() {
      document.removeEventListener('click', this.documentClick);
    }   
};
// Parent component.
const ParentTemplate = `
    <child class="m-item-selector">
    <div
      class="m-item-selector__item"
      slot="dropdownToggle"
    >
       {{itemSelected}} 
    </div>
    <ul
      slot="dropdownContent"
      class="m-item-selector__item-list"
    >
      <li
        class="m-item-selector__item-wrapper"
        v-for="(item, index) in items"
        :key="index"
      >
        <button
            class="m-item-selector__item"
          @click="selectItem(item)"
         >
            {{item}}
         </button>
      </li>
    </ul>
  </child>
`;
const Parent = {
    template: ParentTemplate,
    data() {
      return {
        items: ['item 1', 'item 2', 'item 3'],
        itemSelected: 'item 1',
      };
    },
    components: {
      Child,
    },
    methods: {
      selectItem(item) {
        console.log(item);
        // I want to close my child component in this method but
        // 'this' represents my parent component.
        // How can I access my child component so that
        // I can do something like this: this.close() ?
        this.itemSelected = item;
      },
    }   
};

// Register components.
Vue.component('parent', Parent);
Vue.component('child', Child);

new Vue({
  el: "#app"
})

Here's the fiddle: https://jsfiddle.net/eywraw8t/19300/

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
ChucKN0risK
  • 395
  • 2
  • 7
  • 18
  • Maybe try looking at solutions with [`$emit`](https://vuejs.org/v2/api/#vm-emit). – Ulysse BN Apr 06 '18 at 14:50
  • `$emit` should be used to communicate from a child component to a parent component. In my case, I want to do the opposite :/ – ChucKN0risK Apr 06 '18 at 14:54
  • 2
    Just define a variable and pass it as a prop to the child. Set it it to false when you want the child to be closed. – Ohgodwhy Apr 06 '18 at 15:08
  • 1
    Please include a [mcve] in the question itself. The jsfiddle can exist as an additional help, but not on its own. – Emile Bergeron Apr 06 '18 at 15:17
  • 1
    @EmileBergeron Updated ;) – ChucKN0risK Apr 06 '18 at 15:29
  • 1
    **If a prop doesn't work for you like [Ohgodwhy mentions](https://stackoverflow.com/questions/49695431/vuejs-call-child-method-using-slot?noredirect=1#comment86404364_49695431),** you could catch the click on the parent, then the [parent could call the child component function using its `ref`](https://stackoverflow.com/a/45463576/1218980) – Emile Bergeron Apr 06 '18 at 15:37
  • You made my day @EmileBergeron Thanks a lot! Works like a charm. – ChucKN0risK Apr 06 '18 at 15:53
  • 1
    Possible duplicate of [How to call function on child component on parent events](https://stackoverflow.com/questions/42632711/how-to-call-function-on-child-component-on-parent-events) – Emile Bergeron Apr 06 '18 at 15:56
  • I wrote an [extensive answer on Vue communication](https://stackoverflow.com/a/49702934/1218980) if you want to take a look at it. – Emile Bergeron Apr 07 '18 at 12:49

1 Answers1

0

With a default slot this should work: this.$slots.default[0].context.close()

close() should be defined in your component's methods section

If you are using named slots, just replace default[0]

Ngoral
  • 4,215
  • 2
  • 20
  • 37