Building a Custom Vue Dropdown Component with Popper.js

I am currently working on creating a dropdown component using vue and popperjs. Just to clarify, I am utilizing vuejs v.2.6.12 and popperjs v.2.9.2. Here is the code snippet:

<template>
        <button type="button" @click="show = true">
            <slot />
            <portal v-if="show" to="dropdown">
                <div>
                    <div
                        style="position: fixed; top: 0; right: 0; left: 0; bottom: 0; z-index: 99998; background: black; opacity: .2"
                        @click="show = false"
                    />
                    <div
                        ref="dropdown"
                        style="position: absolute; z-index: 99999;"
                        @click.stop="show = autoClose ? false : true"
                    >
                        <slot name="dropdown" />
                    </div>
                </div>
            </portal>
        </button>
    </template>
    
    <script>
    import { createPopper } from "@popperjs/core";
    
    export default {
        props: {
            placement: {
                type: String,
                default: "bottom-end"
            },
            boundary: {
                type: String,
                default: "scrollParent"
            },
            autoClose: {
                type: Boolean,
                default: true
            }
        },
        data() {
            return {
                show: false
            };
        },
        watch: {
            show(show) {
                if (show) {
                    this.$nextTick(() => {
                        this.popper = createPopper(this.$el, this.$refs.dropdown, {
                            placement: this.placement,
                            modifiers: [
                                {
                                    name: "preventOverflow",
                                    options: {
                                        boundary: this.boundary
                                    }
                                }
                            ]
                        });
                    });
                } else if (this.popper) {
                    setTimeout(() => this.popper.destroy(), 100);
                }
            }
        },
        mounted() {
            document.addEventListener("keydown", e => {
                if (e.keyCode === 27) {
                    this.show = false;
                }
            });
        }
    };
    </script>

Upon trying to run this code, I encountered an error message stating

Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.
. I have double-checked and believe that I have correctly placed the reference this.$el and the popper this.$refs.dropdown.

Can anyone offer assistance in resolving this issue?
Thank you

Answer №1

I successfully developed a code snippet that functions flawlessly, free from any errors.

  • The utilization of vue-portal necessitates global importation in your code; failing to do so requires manual importing in the Single File Component (SFC).
  • A crucial step is creating <portal-target> somewhere within the codebase. Otherwise, this.$refs.dropdown will not exist, as it acts as the default content of the portal.

const createPopper = Popper.createPopper

/* import {
  createPopper
} from "@popperjs/core"; */

Vue.component('DropDown', {
  props: {
    placement: {
      type: String,
      default: "bottom-end"
    },
    boundary: {
      type: String,
      default: "scrollParent"
    },
    autoClose: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      show: false
    };
  },
  watch: {
    show(show) {
      if (show) {
        this.$nextTick(() => {
          this.popper = createPopper(this.$el, this.$refs.dropdown, {
            placement: this.placement,
            modifiers: [{
              name: "preventOverflow",
              options: {
                boundary: this.boundary
              }
            }]
          });
        });
      } else if (this.popper) {
        setTimeout(() => this.popper.destroy(), 100);
      }
    }
  },
  mounted() {
    document.addEventListener("keydown", e => {
      if (e.keyCode === 27) {
        this.show = false;
      }
    });
  },
  template: `
    <button type="button" @click="show = true">
      <slot />
      <portal
        v-if="show"
        to="dropdown"
      >
        <div>
          <div
            style="position: fixed; top: 0; right: 0; left: 0; bottom: 0; z-index: 99998; background: black; opacity: .2"
            @click="show = false"
          />
          <div
            ref="dropdown"
            style="position: absolute; z-index: 99999;"
            @click.stop="show = autoClose ? false : true"
          >
            <slot name="dropdown" />
          </div>
        </div>
      </portal>
    </button>
  `
})

new Vue({
  el: "#app",
  template: `
    <div>
      <drop-down>
        <template
          v-slot:default
        >
          Dropdown default slot
        </template>
        <template
          v-slot:dropdown
        >
          This is the dropdown slot content
        </template>
      </drop-down>
      <portal-target
        name="dropdown"
      />
    </div>
  `
})
<script src="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="ed9b9888addfc3dbc3dcdf">[email protected]</a>"></script>
<script src="http://unpkg.com/portal-vue"></script>
<script src="https://unpkg.com/@popperjs/core@2"></script>
<div id="app"></div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The proper method for converting an AxiosPromise to a standard Promise without falling into the promise constructor anti pattern

Here is a TypeScript function example: public load(filter: IFilter): Promise<Material[]> { return axios.get<Material[]>("data.json"); } When using TypeScript, an error may occur due to incompatible types: [ts] Type 'AxiosPromise< ...

Directing attention to a concealed element

I need help triggering a focus event after an element is recognized as focusable within the DOM. I have successfully shown form elements based on previous input, now I need to set focus for user input. Currently, the function provided is not working unle ...

"angular-seed's web-script.js and cross-origin resource sharing (cors

I'm having trouble for the second day... I'm attempting to retrieve some json from an external domain, but I'm facing CORS issues. I believe that the problem lies with my node.js server not sending Origin: http://api.bob.com I've res ...

Enlarging a JSON structure exported from Clara.IO using Three.js

As a beginner in Three.JS, I have been honing my skills in creating scenes and models. Recently, I imported a washing machine model into my scene and positioned it accordingly. Now, I am trying to scale it up to make it larger, but I am unsure of how to do ...

The outer DIV will envelop and grow taller in conjunction with the inner DIV

Could use a little help here. Thank you :) I'm having trouble figuring out how to get the outer div to wrap around the inner div and expand upwards with the content inside the inner editable div. The inner div should expand from bottom to top, and t ...

Binding text inputs in Laravel Vue.js does not work

I am working on building a basic edit application for a post using Laravel. I am utilizing Vue.js 2 to bind the data, but I am encountering an issue where the data is not displaying as expected. When I use Vue's syntax, such as @{{ postData.title }}, ...

Incorporate a JavaScript form into a controller in MVC4

I'm facing an issue where I need to trigger a JavaScript function from within a controller method in my project. Here is the code snippet that I am using: Public Function redirectTo() As JavaScriptResult Return JavaScript("ToSignUp()") E ...

Issue with Observable binding, not receiving all child property values in View

When viewing my knockout bound view, I noticed that not all values are being displayed. Here is the script file I am using: var ViewModel = function () { var self = this; self.games = ko.observableArray(); self.error = ko.observable(); se ...

Perform fn(param1, param2) within a callback using AngularJS ng-click syntax

When using AngularJS (and likely in other frameworks as well), you have the ability to pass a function with parameters to ng-click like this: <div ng-click="myFunc(param)">Click Me</div> I am interested in achieving the same functionality by ...

Configuring the Port for NodeJS Express App on Heroku

Currently, I am in the process of hosting my website on Heroku and configuring everything to ensure my app is up and running smoothly. However, each time I attempt to submit the form, undefined errors occur. For more details on the Undefined Errors and Co ...

Best practices for managing login authentication using MongoDB in a RESTful API

I'm currently figuring out how to verify if the values align with the MongoDB data. In my approach, I'm utilizing the PUT method along with attempting to utilize findOneAndUpdate to validate the values. <script> const logindetails = new Vu ...

The code for the Express app.get() method on the server is not being updated, although other parts of the

Recently, I encountered an issue with my node js and express server that is running on localhost. I made a change in the server code from: app.get('/', (req, res) => { res.sendFile(__dirname + '/public/index.html'); }); To: app. ...

What purpose do #tags serve in a Vue template?

I am having trouble finding any documentation that specifically explains the purpose of the #tags within the <template> element. For example: <script setup> import { ref } from 'vue'; import { useForm } from '@inertiajs/inertia- ...

Top methods for handling special characters in a database

My mysql database is filled with json data that is then used in angularjs for display purposes. Some of the text within the database includes escaped quotes and double quotes, for example: Do you possess at least a bachelor\'s degree in child ...

What is the best approach for handling nested conditional statements?

I have a complex set of nested conditional checks that I am working on refining to avoid getting stuck in a nested if{}else{} loop. To provide context, I am dealing with two objects, CACHE_FILE_AGE_LIMIT and CACHE_FILE_CURRENT_AGE, and I am trying to navig ...

Designing a personalized Firebase data message handler for Vue to respond to the original application when receiving a push notification

My Vue app is connected to Firebase messaging service where a service worker handles push notifications. The issue arises when a firebase data message push notification triggers the service worker, as I want a postMessage to be sent back to the app. Howeve ...

Creating a multi-tiered dropdown menu in the navigation bar with the help of Bootstrap 4 and Angular 7

My goal is to implement a multilevel dropdown using bootstrap 4 and angular 7. While I successfully created a simple dropdown in the navbar following the official bootstrap documentation, I struggled to make the multilevel dropdown work. After referring ...

Fade in images smoothly

Currently, I am in the process of creating a visually appealing image "slider" for a landing page on one of my websites. The slider that I have already created is fully functional and successful, but I am looking to take it up a notch... My goal is to inc ...

Creating a Popup with a Hazy Background: A Step-by-Step Guide

I've been working on implementing a popup on my existing page that opens 4.5 seconds after the page loads. I want to add a blur effect to the background when the popup appears. I've tried various methods to add the blur effect to the main div, bu ...

How can I add .htaccess to Vue production when using npm run build?

While using Vue CLI 3 and vue-router with history mode, I encountered this particular issue. Upon further investigation, I discovered that inserting a .htaccess file inside the dist folder after running npm run build resolved the issue. Is there a way to ...