Redback87
Redback87

Reputation: 47

Load nested JSON array into select in Vue using computed properties

Originally in my Vue component I had a series of nested if statements that would go through the JSON data to determine whether a text input should be displayed or a select based on a has_selectable_value option being true (select display) or false (text input display), and if it was a select then loop through the data and output associated options.

I have been able to change that to a computed statement which almost does everything I need it to do apart from one little thing which is to display the select options.

Here is the relevant part of the Vue Code:

<template v-else-if="searchtype == 9">
    <select v-for="service in selectableServices" class="form-control" v-model="searchvalue" required>
        <option value="">Select A Location</option>
        <option v-for="sl in selectableLocations" :value="sl.location_id">{{sl.name}}</option>
    </select>
    <input v-for="service in nonSelectableServices" class="form-control" v-model="searchvalue" placeholder="Enter Search Value" required>
</template>

The current computed functions:

services: function () {   
    var ret = []
    this.countries.forEach(function(country) {
        country.states.forEach(function(state) {
            state.services.forEach(function(service) {
                ret.push(service)
            });
        });
    });

    return ret;               
},
selectableServices: function () {
    return this.services.filter(service => service.id == this.service && service.has_selectable_location);
},
nonSelectableServices: function () {
    return this.services.filter(service => service.id == this.service && !service.has_selectable_location);
},
selectableLocations: function () {
    // Filter one more level down
    return this.selectableServices.map(service => service.selectablelocations);
},

This is the JSON data structure I am working with as well (I cut it back to the relevant parts for this part of the code):

[
    {
    "id": 1,
    "name": "Country Name",
    "states": [
        {
            "id": 1,
            "name": "State Name",
            "services": [
                    {
                    "id": 1,
                    "name": "Service Name",
                    "has_selectable_location": 1,
                    "selectablelocations": [
                            {
                                "id": 1,
                                "name": "Selectable Location A",
                            },
                        ]
                    }
                ]
            }
        ]
    }
]

Using a Vue plugin for Chrome I can see that the computed function selectableLocations loads an array containing the individual locations, but the existing v-for statement isn't able to function correctly. Instead I still need to go down one more level which I can do by adding an extra v-for loop like so:

<template v-for="selectableLocationsList in selectableLocations" >
    <option v-for="sl in selectableLocationsList"  :value="sl.location_id">{{sl.name}}</option>
</template>

Everything displays correctly, but I am not sure if this is best practice as I was hoping to do as much of this in a computed function as possible and ideally only require a single v-for statement. But if it's not possible like that I understand and I can leave it as is.

Thank you in advance.

Edit: After more testing and research I have come up with this code that works as I had desired:

var formArray = []
var locationsArray = this.servicesArray.filter(service => service.id == this.service);

locationsArray.map(service => service.selectablelocations);

locationsArray.forEach(function(selectableLocations) {
    selectableLocations.selectablelocations.forEach(function(location) {
        formArray.push(location)
    });
});

return formArray;

Is there a way I can refactor this further and make it a bit cleaner?

Upvotes: 0

Views: 1380

Answers (1)

kevguy
kevguy

Reputation: 4438

Solely considering the code you posted after the Edit , the code can be refactored this way:

let formArray = [];

formArray = this.servicesArray
  .filter(service => service.id == this.service)
  .map(service => service.selectablelocations)
  .reduce((prev, curr) => prev.concat(curr))

return formArray

Note that the map you used doesn't do anything as Array.prototype.map only returns a new array, but you didn't assign it to anything.

Upvotes: 0

Related Questions