запуск именованного компонента при щелчке и управление элементом отключения щелчка

#vue.js

Вопрос:

Я создаю Dropdown.vue компонент. У него есть опора, называемая name так, чтобы я мог запустить ее с помощью любого div, который я хочу, с помощью события щелчка.

Например, у меня есть элемент <button @click="openDropdown('notifications-dropdown')">test</test>

тогда у меня есть компонент: <dropdown name="notifications-dropdown"><div>content</div></dropdown>

Событие щелчка обрабатывается из глобального объекта Vue в методах:

 methods: {
        openDropdown( name) {
            EventBus.$emit('dropdown-opened', name);
        }
    }
 

На фактическом раскрывающемся компоненте у меня есть в смонтированном методе:

 EventBus.$on('dropdown-opened', (name) => {
 if (this.name == name) {
    this.active = true;
 }
});
 

Все это работает нормально, однако мне нужно обработать функциональность элемента click off, чтобы, когда выпадающий список не щелкается, active логическое значение было изменено на false.

в раскрывающемся списке у меня есть:

 methods: {
        closeDropdown(e) {
            if ( !this.$el.contains(e.target) ) {
                this.active = false;
            } 
        }
    }
 

и в mounted и beforeDestroy , у меня есть:

document.removeEventListener('click', this.closeDropdown)

Затем возникает проблема: при первом нажатии кнопки, которая запускает раскрывающийся список, также запускается событие click off элемента. таким образом, раскрывающийся список открывается и сразу же закрывается

Как я могу сделать так, чтобы при первом нажатии кнопки, которая запускает выпадающий список, также не запускался e.target щелчок closeDropdown() выключателя .

Но затем, если вы снова нажмете на спусковой крючок, я ожидаю, что выпадающий список закроется.

Below is the full code:

Dropdown.vue

 <template>
    <Transition name="fade-in-scale">
        <div @click.stop ref="thedropdown" v-if="active" class="dropdown-menu absolute bg-white border-l border-b border-r border-gray-200 top-100  w-72 right-0 md:w-84">
            <slot></slot>
        </div>
    </Transition>
</template>

<script>
// @ is an alias to /src

export default {

    name: 'Dropdown',
    data() {
        return {
            active: false
        }
    },
    props: {
        name: String
        
    },
    computed: {
      
    },
    mounted() {
        document.addEventListener('click', this.closeDropdown);

        EventBus.$on('dropdown-opened', (name) => {
            if (this.name == name) {
                this.active = !this.active;
            }
        });
       
    },

    beforeDestroy () {
        document.removeEventListener('click', this.closeDropdown)
    },

    watch: {
        
    },
    methods: {
        closeDropdown(e) {
            if ( !this.$el.contains(e.target) ) {
                this.active = false;
            } 
        }
    }
}
</script>


<style lang="scss" scoped>

    .dropdown-menu {
        top:calc(100%   5px);
    }

    .fade-in-scale-enter-active,
    .fade-in-scale-leave-active {
        transition: all 0.12s;
        transform:scale(1) ;
        opacity:1;
    }
    .fade-in-scale-enter,
    .fade-in-scale-leave-to {
        transform: scale(0.90);
        opacity: 0;
    }
    
</style>
 

app.blade.php

 <button @click="openDropdown('notifications-dropdown')" type="button" class="p-2 outline-none focus:outline-none"></button>
<dropdown name="notifications-dropdown"></dropdown>
 

app.js

 require('./bootstrap');

/**
 * UI
 */
import Btn from './components/ui/Button.vue';
import Dropdown from './components/ui/Dropdown.vue';

const app = new Vue({
    el: '#app',
    components: {
       Btn,
       Dropdown
    },
    methods: {
        openDropdown( name) {
            EventBus.$emit('dropdown-opened', name);
        }
    }
});
 

Ответ №1:

Используйте .stop модификатор события на кнопке открытия @click , чтобы распространение события было остановлено и не достигло обработчика документа click :

 <button @click.stop="openDropdown(...)">
 

ДЕМОНСТРАЦИЯ

Также имейте в виду утечку памяти Dropdown.vue , так как это не удаляет прослушиватель событий из шины событий. Это легко исправить , объявив метод компонента для openDropdown и используя его для добавления прослушивателя событий mounted и его удаления в beforeDestroy :

 export default {
    mounted() {
        EventBus.$on('dropdown-opened', this.openDropdown);
    },

    beforeDestroy () {
        EventBus.$off('dropdown-opened', this.openDropdown)
    },

    methods: {
        openDropdown(name) {/*...*/}
    }
}
 

Комментарии:

1. Идеально! Большое спасибо. Я беспокоился, что мне придется переосмыслить то, как я все делаю. Мне придется разобраться с утечками памяти и Vue. Я добавил методы mounted и beforeDestroy. Спасибо, спасибо тебе!

2. Почему вы используете шину событий для открытия раскрывающегося списка? Я бы просто поместил ссылку на шаблон в раскрывающемся списке, чтобы вызвать его openDropdown метод.

3. Изначально я создавал отдельный компонент для каждого выпадающего списка. Я возвращаюсь и рефакторингую компоненты, чтобы элементы могли просто вызывать выпадающий список, в котором используются слоты. Можете ли вы показать мне пример того, что вы имеете в виду? Я использовал шину только потому, что не знаю другого способа глобального вызова определенного экземпляра компонента. все еще довольно новичок в vue и у него есть вредные привычки

4. Ограниченные слоты — это определенно правильный путь.

5. Быстрый рефакторинг состоял бы в том, чтобы родитель поместил ссылку на шаблон в раскрывающийся список ( <dropdown ref="dropdown"> ) и вызвал его openDropdown метод из обработчика кнопки открытия click ( <button @click="$refs.dropdown.openDropdown()">Open</button> )