Creating a dropdown menu that interacts with a specific element: step-by-step guide

TL;TR

How can I achieve the following:

getMyComponent(selectedMyComponentID).complexOperation()

This seems like a simple and practical task, such as selecting an item from a dropdown menu.

More Information

Imagine that I am creating some sort of editor (like a todo list or similar application). The GUI includes the concept of a "selected" element. (In our case, it is the currently active bootstrap nav-tab), and I want to have dropdown menus with options that perform various operations on the selected element.

If I have the id of the "selected" component, obtaining a reference to the MyComponent with a complexOperation() method corresponding to that id proves to be more challenging than expected.

Maybe I'm not approaching this in "the Vue way".

I see several ways to achieve

complexOperationOnSelectedMyComponent()
:

  • Using refs - seems messy and unattractive
  • Refactoring the complexOperation() out of MyComponent and into a new MyData, so that the business logic on the data is utilized by both App.vue and MyComponent.vue. This approach involves changing data and therefore props in the parent component, which aligns with Vue principles. However, it leads to excess boilerplate since every operation in each component requires two versions. I personally dislike redundancy and boilerplate...
  • Utilizing vuex? Perhaps I'm not ready for that yet...
  • Implementing an "event bus" Vue instance and emitting events from parent to child components. Seems excessive. It's messy and results in additional boilerplate.

Am I overlooking something? Isn't this a common requirement?

Details

For demonstration purposes, let's consider a template:

<template>
  <div id="app">
    <div v-for="elem in mydata" :key="elem.id" @click="setSelected(elem)">
      <MyComponent :value="elem"/>
    </div>
    <button @click="complexOperationOnSelectedComponent">
        Complex operation on Selected Component
    </button>
  </div>
</template>

and a predefined data structure where the first element is initially selected:

data() {
  return {
    mydata: [
      { id: 0, foo: "bar", selected: true },
      { id: 1, foo: "baz", selected: false },
      { id: 2, foo: "world", selected: false }
    ]
  };
}

(View complete code on codesandbox)

There is a button labeled "Complex operation on Selected Component". What should be implemented in the

complexOperationOnSelectedComponent
method?

The codesandbox above also includes equivalent buttons within each MyComponent. These buttons simply trigger a complexOperation() method defined in the MyComponent.

In my opinion, whether the button is located inside or outside the component is trivial. The goal is to obtain a reference to the MyComponent for the selected id and invoke

selectedComponent.complexOperation()
within the menu item's @click event handler.

In our actual scenario, the user selects the "component" by clicking on a navigation bar (not directly on the MyComponent instance), resulting in an id (mydata[n].id as mentioned earlier).

Using refs

One option could be to assign ref="components" in the <MyComponents> definition. As it is within a v-for loop, this.$refs.components will represent an array of MyComponents. By identifying the correct one based on the id, it can be utilized.

Since there is no certainty about the order in this.$refs.components, a search for the selectedMyComponentID may be required each time, but it's manageable...

Is this truly the optimal solution?

Answer №1

When dealing with elements in the DOM that are not connected to any Vue instance properties, using $refs can be beneficial. This allows direct access to these elements and provides a way to store which element is selected in data.

The decision of whether to place the method in the parent or child component depends on the need for logic in other areas. If actions need to be performed within the child component, it makes sense to house the logic there.

An alternative approach could involve using an "event bus" Vue instance to emit events from child to parent. However, this may be considered overkill for simpler cases like this one.

In Vue.js, props flow from parent to child components, while events are emitted from child to parent components. While Vuex and event busses have their advantages in larger applications, they may not be necessary in scenarios like the current one. It's important to emit changes to props rather than directly manipulating them as shown in MyComponent.

A refactored version of the code emphasizes avoiding direct manipulation of prop values: https://codesandbox.io/s/button-onclick-on-selected-child-lf37c?fontsize=14

<template>
  <div id="app">
    <div v-for="elem in mydata" :key="elem.id" @click="selectedElem = mydata[elem.id]">
      <MyComponent :value="elem" :reverse="elem.reverse"/>
    </div>
    <button @click="reverseSelectedFoo">Reverse Selected Foo</button>
  </div>
</template>

<script>
import MyComponent from "./components/MyComponent";

export default {
  name: "App",
  data() {
    // Consider retrieving this data from an API
    return {
      mydata: [
        { id: 0, foo: "bar", reverse: false },
        { id: 1, foo: "baz", reverse: false },
        { id: 2, foo: "world", reverse: false }
      ],
      selectedElem: null
    };
  },
  methods: {
    reverseSelectedFoo() {
      this.selectedElem.reverse = !this.selectedElem.reverse;
    }
  },
  components: {
    MyComponent
  }
};
</script>

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

How do I resolve validation function error messages in Vuetify?

When utilizing validation methods in Vuetify, I encountered the following error message↓ I simply want to create a form validation check and implement a function that triggers the validation when the 'submit' button is clicked. I believe my i ...

Leveraging a single Vuex module store across various sibling components

In my application, I am working with one global state that contains several modules. Currently, I have Vue components set up for different sections of my page. Everything is properly configured so that /foo utilizes the foo store (which is functioning co ...

Sorting Json Data Using Vue

My experience with Vue.js led me to a challenge I can't quite figure out: how to filter specific data in a Json file. Let me provide more context: I have a Json file filled with various electronics products such as computers, mice, keyboards, etc. I w ...

What steps do I need to take in order to implement a functional pagination menu in Vue?

I downloaded and installed laravel-vue-pagination with the following command: npm install laravel-vue-pagination After that, I globally registered it in my app.js file: Vue.component('pagination', require('laravel-vue-pagination')); F ...

Dealing with CORS policy or 404 errors when using Vue.js and socket.io in conjunction with the npm run serve command

I'm currently working on developing my project locally. Running on my local machine is a simple socket.io server: const io = require('socket.io')(); io.listen(3000); In my Vue.js application, I aim to establish a connection with a socket ...

Error: Unable to access the 'width' property of an undefined value - Nuxt JS and Canvas

I am new to using Vue JS and trying to run a sketch on the canvas element in Nuxt JS, but encountering some issues. While there are no errors in VS Code, there is an error showing up in the browser console: client.js?06a0:84 TypeError: Cannot read propert ...

Using Font Awesome icons with Buefy for beautiful designs

Currently, I am in the process of transitioning my project from utilizing bulma + jQuery to buefy. The resources I am loading include buefy, vue, and font awesome from a CDN. However, despite specifying the defaultIconPack as 'fas' for font aweso ...

How to utilize a PHP array within a Vue.js template

I have been exploring the realms of Laravel and vue.js recently and have encountered a challenge. Within my Laravel model, I have a PHP method that retrieves data from a database and organizes it into objects stored in an array. Now, my goal is to access t ...

Error: Attempting to access the 'getCroppedCanvas' property of an undefined value in VueJs

I've been exploring the vue-cropperjs library, but every time I execute the code, I encounter error messages: Uncaught TypeError: Cannot read property 'getCroppedCanvas' of undefined Uncaught TypeError: Cannot read property 'replace&ap ...

Protecting Your Routes with Guards in Framework 7 Vue

Whenever I utilize the following code snippet: { path: '/chat/', async(routeTo, routeFrom, resolve, reject) { if (localStorage.getItem('token')) { resolve({ component: require('./assets/vu ...

Creating a map-like scrolling feature for a full-sized image, allowing both horizontal and vertical movement

My goal is to create an infographic image that can be scrolled horizontally and vertically, zoomed in or out, and where I can add markers just like Google Maps. I attempted the solution of using two scroll views as suggested here: https://github.com/faceb ...

You cannot convert a function to a string while utilizing axios get in nuxtServerInit

While attempting to connect my app to the backend using Udemy's Nuxt.js course, I encountered a GET http://localhost:3000/ 500 (Internal Server Error) on the client side with the following code: import Vuex from 'vuex'; import axios from &a ...

When using v-for to render an array list fetched from AsyncData, an error is thrown: The virtual DOM tree rendered on the client-side does not match the one

For my application, I am utilizing Nuxt.js. On one of the pages, I am using AsyncData to fetch an array of data objects from my API asynchronously. These data objects are then rendered in my template using v-for. Everything is functioning properly until I ...

Upon reinstalling the node_modules in my Nuxt.js project, I encountered the error message "Must use import to load ES Module:~"

After reinstalling node_modules, I encountered an issue where ufo and node-fetch were missing. Upon adding them back in, running npm run dev resulted in an error when opening localhost in a browser. This same error persisted when cloning the project onto ...

Encountering a `ECONNREFUSED` error while attempting to dispatch an action in the

I have decided to convert my Nuxt application to SSR in order to utilize nuxtServerInit and asyncData. Below are the steps I followed during this conversion process. Removed ssr: false from nuxt.config.js Dispatched actions to initialize the store's ...

Encountering difficulties while trying to access the SQLite database file through a JavaScript Axios GET request

Having trouble opening an sqlite DB file from a js axios.get request which is resulting in an exception message being outputted to the console. The request is supposed to call my PHP controller to retrieve data from the DB and return it json-encoded. On t ...

The feature of Nuxt 3's tsconfig path seems to be malfunctioning when accessed from the

Take a look at my file structure below -shared --foo.ts -web-ui (nuxt project) --pages --index.vue --index.ts --tsconfig.json This is the tsconfig for my nuxt setup. { // https://v3.nuxtjs.org/concepts/typescript "exte ...

Guide on verifying the presence of a value in a textbox using vue

I'm currently working on a form that requires only numbers and text input. Any characters like ., ,, or spaces are not allowed in the textbox. Here are some of the attempts I've made, but unfortunately, they did not yield the desired results: i ...

Using Vuetify to highlight selected radio buttons in an edit form

One issue I am encountering involves editing a table row. After clicking on the edit button, a form pops up with the data pre-filled for editing purposes. However, the radio button selected previously does not display as checked in the form; both options a ...

Exploring the process of introducing a new property to an existing type using d.ts in Typescript

Within my src/router.ts file, I have the following code: export function resetRouter() { router.matcher = createRouter().matcher // Property 'matcher' does not exist on type 'VueRouter'. Did you mean 'match'? } In an ...