Janne
Janne

Reputation: 1151

get selected object from options loop

I'm trying to find a way to bind array of objects within Vue select-element. The case is somewhat as follows:

data: {
  ideas: [
    { id: 1, code: "01A", text: "option 1", props: [] }, 
    { id: 2, code: "02A", text: "option 2 , props: [{ details: "something" }]}
  ]},
  currentForm: {
    something: "foo",
    else: "bar",
    ideaCode: "01A",
    text: "option 1"
  }
];

... and in HTML ...

<select v-model="currentForm.ideaCode" @change="setCodeAndLabelForForm(???)">
  <option v-for="i in ideas" value="i">{{ i.text }}<option>
</select>

Basically I need to be able to track which object user selects, trigger my own change-event, all the while having binding with a single key from another object... selected value / reference-key should be separated from user-selected option/object. Note: currentForm is not same object-type as option! It only contains some of those properties which option happens to have, and which I'm trying to transfer to options by triggering change-event for user-selection.

The problem is I haven't figured out how to pass currently selected value for the function OR how to write something like:

<select v-model="selectedIdea" @change="setCodeAndLabelForForm" :track-by="currentForm.ideaCode">
  <option v-for="i in ideas" value="i">{{ i.text }}<option>
</select>

One possible (and working) approach is:

<select v-model="currentForm.ideaCode" @change="setCodeAndLabelForForm">
  <option v-for="i in ideas" value="i.ideaCode">{{ i.text }}<option>
</select>

setCodeAndLabelForForm: function() {
    var me = this;
    this.ideas.forEach(function(i) {
        if(i.ideaCode == me.currentForm.ideaCode) {
            me.currentForm.ideaCode = i.selectedIdea.ideaCode;
            me.currentForm.text = i.text;
            ... do stuff & run callbacks ...
        }
    });
}

... but it just seems terrible. Any better suggestions?

Upvotes: 5

Views: 23351

Answers (6)

Fandi Susanto
Fandi Susanto

Reputation: 2453

A little bit better workaround: use index in v-for

<select v-model="selIdeaIndex" @change="setCodeAndLabelForForm">
  <option v-for="(i,idx) in ideas" value="idx">{{ i.text }}<option>
</select>

For the js:

data: {
  selIdeaIndex:null,
  ideas: [
    { id: 1, code: "01A", text: "option 1", props: [] }, 
    { id: 2, code: "02A", text: "option 2", props: [{ details: "something" }]}
  ]
},
methods:{
    setCodeAndLabelForForm: function() {
        var selIdea = this.ideas[this.selIdeaIndex];
        //Do whatever you wanna do with this selIdea.
    }
}

Upvotes: 3

Janne
Janne

Reputation: 1151

It's been a while since I asked this question and there have been good suggestions for handling this situation. I cannot remember the exact business-case presented here, but just by a quick glance it looks like I couldn't figure out how to set/initialize right selection afterwards, because handling just the @change event is childs play -- it's the pairing of one single value against list of object-based-options which is harder. What I was most likely looking for was something AngularJS used to have (track-by -property, which matches any given value against selected-option).

Personally now-a-days I would separate UI-logics instead of trying to force 'em to blend together. Most viable approach for myself would be handling list of options and the selected option as one logical area (ie. data: { list: [...options], selectedIdea: Object }) and separate the "currentForm"-object from selection. Let's break this out:

  • selectedIdea is something which needs to trigger change into currentForm-object. It's not any kind of hybrid-model, it's just a plain object, one of the available selections, pure and simple.
  • ... and once again: Whenever selectedValue === one of the options, the select-dropdown is automatically set to right selection.
  • "currentForm"-object has a property which can be used to set the selectedIdea. In this case it's the "ideaCode". This ideaCode doesn't automatically do any pairing or such Component logic needs to represent the rules, which trigger selecting the correct option, which matches "ideaCode".
  • Just an extra-though: selectedIdea and currentForm-object are two different logical elements. They could be even separated to different components if one would want do, and in some cases it's really good thing to separate 'em.

So by these statements I guess I would change my select's v-model to be exactly what it's supposed to be: One of the selected objects (change v-model="currentForm.ideaCode" into v-model="selectedIdea" or such). Then I would simply add watcher for that selectedIdea and make any alterations to currentForm-object from there.

How about initializing that option by currentForm.ideaCode ? Do one of the following on create-method:

  • Iterate list of available options. When you find option where currentForm.ideaCode == option.code => this.selectedIdea = option
  • ... or use ecmascript find-method to do the same
  • ... or use underscore/lodash find-method to do the same

Another way would be by using computed value, as suggested Augusto Escobar. Also $ref would work, as suggested by feng zhang, but this approach would still require solution for initializing correct option afterwards (when loading editor with initial values). Thanks to Bhojendra Rauniyar as well -- you were right all along, but I just couldn't comprehend the answer as I couldn't have figured out how to backtrack initial selection.

Thanks for all the suggestions over the year!

Upvotes: 0

zjffun
zjffun

Reputation: 1243

A humble way is using $ref.

There is a solution using $ref and @change.

Vue.js get selected options' raw object

Upvotes: 0

acdcjunior
acdcjunior

Reputation: 135762

If you know your options will only come from that v-for="i in ideas" then the <option> indexes will be the same as the item indexes.

Thus <select>.selectedIndex will be the index of the selected this.item.

new Vue({
  el: '#app',
  data: {
    ideas: [
      { id: 1, code: "01A", text: "option 1", props: [] }, 
      { id: 2, code: "02A", text: "option 2" , props: [{ details: "something" }]}
    ],
    currentForm: {ideaCode: "01A", text: "option 1"}
  },
  methods: {
    setCodeAndLabelForForm: function(selectedIndex) {
      var selectedIdea = this.ideas[selectedIndex];
      this.currentForm = {ideaCode: selectedIdea.code, text: selectedIdea.text};
    }
  }
})
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>

<div id="app">
  <select v-model="currentForm.ideaCode" @change="setCodeAndLabelForForm($event.target.selectedIndex)">
    <option v-for="i in ideas" :value="i.code">{{ i.text }}</option>
  </select>
  <br> currentForm: {{ currentForm }}
</div>

Differences from yours: @change="setCodeAndLabelForForm($event.target.selectedIndex)" and the setCodeAndLabelForForm implementation.

Upvotes: 0

Augusto Escobar
Augusto Escobar

Reputation: 36

I don't know if this is the best solution, but I solve this problem using computed properties like this:

In the JavaScript file (ES6):

data () {
    return {
        options: [
            { id: 1, text: "option 1" },
            { id: 2, text: "option 2" }
        ],
        selectedOptionId: 1
    }
},
computed: {
    selectedOption () {
        return _.find(this.options, (option) => {
            return option.id === this.selectedOptionId
        });
    }
}

In the HTML file:

<select v-model="selectedOptionId">
  <option v-for="option in options" :value="option.id" :key="option.id">{{ option.text }}<option>
</select>

The '_' symbol is a common JavaScript library called Lodash and I highly recommend the usage. It can make you save some precious time.

Upvotes: 0

Bhojendra Rauniyar
Bhojendra Rauniyar

Reputation: 85545

You can implement like this:

Create empty object data to track the selected value:

currentForm: {}

Watch currentForm on the model and pass the selected object:

<select v-model="currentForm" @change="setCodeAndLabelForForm(currentForm)">

Pass in the selected value in option: (you were doing right in this step, but I just changed i to idea as it's little confusing looping index)

<option v-for="idea in ideas" :value="idea">{{ idea.text }}<option>

Apply your method:

setCodeAndLabelForForm(selected) {
  // Now, you have the user selected object
}

Upvotes: 5

Related Questions