Reputation: 97
I am pulling information from an API that returns data in the following format:
[
{
"id": 173,
"date": "2020-12-10T16:05:30",
"date_gmt": "2020-12-10T16:05:30",
"guid": {},
"modified": "2020-12-10T16:05:31",
"modified_gmt": "2020-12-10T16:05:31",
"slug": "test",
"status": "publish",
"type": "place",
"link": "http://localhost:81/test/",
"title": {},
"content": {},
"featured_media": 0,
"template": "",
"acf": {
"address": {
"address": "123 Test Address",
"street_number": "123",
"street_name": "Test Address",
"city": "Philipsburg",
"state": "Sint Maarten",
"country": "Sint Maarten",
"country_short": "SX"
},
"header": {}
},
"_links": {}
},
etc
]
I store that in Vuex, and organize the information via the following:
computed: {
resorts() {
const resorts = {};
if (this.$store.state.loading === false) {
this.$store.state.posts.forEach((post) => {
const c = post.acf.address.country;
const s = post.acf.address.state;
//const t = post.title;
resorts[c] = resorts[c] || {};
resorts[c][s] = resorts[c][s] || [];
resorts[c][s].push(post);
});
}
return resorts;
},
}
I'm displaying the information in a v-for
loop like this (Pug):
section.united-states(v-for="(country, index) in resorts" v-if="index==='United States'")
h1(v-html="index")
section.state(v-for="(state, subIndex) in country" :key="subIndex" :class="subIndex.toLowerCase()")
h5(v-html="subIndex")
ul
li(v-for="post, resort) in state")
listing(:id="post.id" :slug="post.slug" :image="post.acf.header" :title="post.title.rendered" :city="post.acf.address.city" :street="post.acf.address.street_name_short")
This displays the information correctly. However, I need it organized alphabetically by Country, then State, then City names. I've tried to sort it and attempted lodash.orderBy
, but could not get the list organized. From the Vue inspector tab in Chrome, the computed countries and states (not cities) appear to be alphabetical. Any suggestions?
Upvotes: 1
Views: 99
Reputation: 138226
One solution is to sort the posts before grouping them by address.
Using Array.prototype.sort()
and String.prototype.localeCompare()
, create a utility (named sortPosts()
) to use in the computed prop that will sort the posts by the country
, state
, city
, then street_name
fields:
const sortPosts = posts =>
posts.slice().sort((a,b) => {
const countryA = a.acf.address.country
const countryB = b.acf.address.country
const stateA = a.acf.address.state
const stateB = b.acf.address.state
const cityA = a.acf.address.city || '' // can be undefined in Google Maps API
const cityB = b.acf.address.city || '' // can be undefined in Google Maps API
const streetA = a.acf.address.street_name
const streetB = b.acf.address.street_name
return countryA.localeCompare(countryB) || stateA.localeCompare(stateB) || cityA.localeCompare(cityB) || streetA.localeCompare(streetB)
})
Now, we'll group these posts using the same logic you already have, but we have to change the data type of the local resorts
variable from Object
to Map
because Object
iteration does not always follow the insertion order, which would break the sorting from sortPosts()
:
export default {
computed: {
resorts() {
// BEFORE:
// const resorts = {};
const resorts = new Map();
if (this.$store.state.loading === false) {
sortPosts(this.$store.state.posts).forEach((post) => {
const c = post.acf.address.country;
const s = post.acf.address.state;
// BEFORE:
// resorts[c] = resorts[c] || {};
// resorts[c][s] = resorts[c][s] || [];
// resorts[c][s].push(post);
if (!resorts.has(c)) {
resorts.set(c, new Map());
}
const stateMap = resorts.get(c);
if (!stateMap.has(s)) {
stateMap.set(s, []);
}
stateMap.get(s).push(post);
});
}
return resorts
},
}
}
As of v2.6.12, v-for
does not support Map
s, so use Array.from()
to make it iterable in v-for
:
<section v-for="[country, countryData] in Array.from(resorts)" :key="country">
<h1 v-html="country" />
<section class="state" v-for="[state, posts] in Array.from(countryData)" :key="state" :class="state.toLowerCase()">
<h5 v-html="state" />
<ul>
<li v-for="(post, resort) in posts" :key="post.id">
...
</li>
</ul>
</section>
</section>
Upvotes: 1