Reputation: 93
I am trying to make a Vue app that lists company offices based on regions. I have a main home view, an offices components, and an office item component. I am using v-for in the offices component to loop through the office items and display them. That works to list them all out. However, I need to sort the office items into separate divs based on the value of "Region". There are 5 regions. I cannot figure out how to loop through them based on that single value.
I know how to import components to one another, but I am trying to loop through all of the office items within the offices component. My guess is to do a loop within a loop, but do I need another component that I'm missing?
office item component:
<div class="office" :class="office.Region">
<p>{{office.Name}}</p>
<p>{{office.Address}}</p>
<p>{{office.Country}}</p>
<p>{{office.Region}}</p>
<p>{{office.Email}}</p>
<p>{{office.Phone}}</p>
</div>
offices component:
<div>
<div v-for="office in offices" :key="office.name">
<div class="office-container global" v-if="office.Region === 'Global'">
<ul>
<li><OfficeItem v-bind:office="office"/></li>
</ul>
</div>
<div class="office-container north" v-if="office.Region === 'North America'">
<ul>
<li><OfficeItem v-bind:office="office"/></li>
</ul>
</div>
<div class="office-container europe" v-if="office.Region === 'Europe, Middle East and Africa'">
<ul>
<li><OfficeItem v-bind:office="office"/></li>
</ul>
</div>
<div class="office-container asia" v-if="office.Region === 'Asia Pacific'">
<ul>
<li><OfficeItem v-bind:office="office"/></li>
</ul>
</div>
<div class="office-container latin" v-if="office.Region === 'Latin America'">
<ul>
<li><OfficeItem v-bind:office="office"/></li>
</ul>
</div>
</div>
</div>
a hardcoded array of objects:
offices: [
{
Name: "Corporate Headquarters",
Address: "Suite 500, 698 West 10000 South, South Jordan, Utah 84095",
Country: "USA",
Region: "Global",
Email: "[email protected]",
Phone: "+1-888-253-6201"
},
{
Name: "EMEA Headquarters",
Address: "First Floor Europa House, Harcourt Street Dublin 2, D02 WR20",
Country: "Ireland",
Region: "Europe, Middle East and Africa",
Email: "[email protected]",
Phone: "+ 353 1 411 7100"
},
{
Name: "India",
Address: "Bagmane Tech Park, Unit No. 4A, Level 2 , Bangalore",
Country: "India",
Region: "Asia Pacific",
Email: "[email protected]",
Phone: ""
},
{
Name: "Brazil",
Address: "Borges de Figueiredo, 303 - 4th floor, Bairro Mooca, São Paulo, SP 03110-010",
Country: "Brazil",
Region: "Latin America",
Email: "[email protected]",
Phone: "+55 11 9 8136 0343"
},
{
Name: "United States (Seattle)",
Address: "1011 Western Ave SW #700, Seattle, WA 98104",
Country: "United States",
Region: "North America",
Email: "[email protected]",
Phone: "+1-206-274-4280"
}
]
I want there to be only 5 office-container divs with the list of corresponding offices in each one. however, I get multiple office-container (i.e. two north America divs) and multiple empty divs inside of those
Upvotes: 0
Views: 7621
Reputation: 90013
[...new Set(this.offices.map(o => o.Region))]
gives you the list of all your regions.
You can loop through this list and and display offices having that region, using a filtering method:
officesOfRegion(region) {
return this.offices.filter(o => o.Region === region)
},
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#hook',
template: '#appTemplate',
data: ({
offices: [{
Name: "Corporate Headquarters",
Address: "Suite 500, 698 West 10000 South, South Jordan, Utah 84095",
Country: "USA",
Region: "North America",
Email: "[email protected]",
Phone: "+1-888-253-6201"
},
{
Name: "EMEA Headquarters",
Address: "First Floor Europa House, Harcourt Street Dublin 2, D02 WR20",
Country: "Ireland",
Region: "Europe, Middle East and Africa",
Email: "[email protected]",
Phone: "+ 353 1 411 7100"
},
{
Name: "India",
Address: "Bagmane Tech Park, Unit No. 4A, Level 2 , Bangalore",
Country: "India",
Region: "Asia Pacific",
Email: "[email protected]",
Phone: ""
},
{
Name: "Brazil",
Address: "Borges de Figueiredo, 303 - 4th floor, Bairro Mooca, São Paulo, SP 03110-010",
Country: "Brazil",
Region: "Latin America",
Email: "[email protected]",
Phone: "+55 11 9 8136 0343"
},
{
Name: "United States (Seattle)",
Address: "1011 Western Ave SW #700, Seattle, WA 98104",
Country: "United States",
Region: "North America",
Email: "[email protected]",
Phone: "+1-206-274-4280"
}
]
}),
computed: {
regions() {
return [...new Set(this.offices.map(o => o.Region))]
}
},
methods: {
officesOfRegion(region) {
return this.offices.filter(o => o.Region === region)
},
displayJson(o) {
return JSON.stringify(o, null, 2);
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script type="text/template" id="appTemplate">
<div id="app">
<div class="region" v-for="region in regions" :key="region">
<hr>
<h3 v-text="region"></h3>
<ul>
<li v-for="(office, i) in officesOfRegion(region)" :key="i">
<pre v-html="displayJson(office)"></pre>
</li>
</ul>
</div>
</div>
</script>
<div id="hook"></div>
I didn't look at your markup, as it's irrelevant. You can use any markup you want once the data is properly sorted.
Here it is with your markup:
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#hook',
template: '#appTemplate',
data: ({
offices: [{
Name: "Corporate Headquarters",
Address: "Suite 500, 698 West 10000 South, South Jordan, Utah 84095",
Country: "USA",
Region: "North America",
Email: "[email protected]",
Phone: "+1-888-253-6201"
},
{
Name: "EMEA Headquarters",
Address: "First Floor Europa House, Harcourt Street Dublin 2, D02 WR20",
Country: "Ireland",
Region: "Europe, Middle East and Africa",
Email: "[email protected]",
Phone: "+ 353 1 411 7100"
},
{
Name: "India",
Address: "Bagmane Tech Park, Unit No. 4A, Level 2 , Bangalore",
Country: "India",
Region: "Asia Pacific",
Email: "[email protected]",
Phone: ""
},
{
Name: "Brazil",
Address: "Borges de Figueiredo, 303 - 4th floor, Bairro Mooca, São Paulo, SP 03110-010",
Country: "Brazil",
Region: "Latin America",
Email: "[email protected]",
Phone: "+55 11 9 8136 0343"
},
{
Name: "United States (Seattle)",
Address: "1011 Western Ave SW #700, Seattle, WA 98104",
Country: "United States",
Region: "North America",
Email: "[email protected]",
Phone: "+1-206-274-4280"
}
]
}),
computed: {
regions() {
return [...new Set(this.offices.map(o => o.Region))]
}
},
methods: {
officesOfRegion(region) {
return this.offices.filter(o => o.Region === region)
},
propsOf(o) {
return Object.keys(o);
}
},
})
.office p {
display: flex;
}
.office p strong {
width: 100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script type="text/template" id="appTemplate">
<div id="app">
<div class="region" v-for="region in regions" :key="region">
<hr>
<hr>
<h3>{{region}}</h3>
<div v-for="(office, i) in officesOfRegion(region)" :key="i" class="office">
<hr>
<p v-for="prop in propsOf(office)"><strong>{{prop}}:</strong> {{office[prop]}}</p>
</div>
</div>
</div>
</script>
<div id="hook"></div>
Upvotes: 1
Reputation: 1960
It looks like the only thing changing in your template is the classes surrounding to the OfficeItem
Component
To keep your code less DRY try applying that conditional logig within the OfficeItem
Component like below.
// OfficeItem.vue
<template>
<li :class="['office-container', getRegionClass(office.Region)]">{{office.Region}}</li>
</template>
<script>
const regional_classes = {
A: 'class_a and-another-A-class',
B: 'class_b and-another-B-class',
C: 'class_c and-another-C-class',
D: 'class_d and-another-D-class',
Z: 'class_z and-another-Z-class'
}
export default {
name: "OfficeItem",
props: {
office: Object
},
methods: {
getRegionClass(region) {
return regional_classes[region] || ''
}
}
};
</script>
Region
and returns a String of whatever case is met within the switch
.
in this Scenario though i feel a regional_class
Object is more readable/maintainable.
And in your Offices
component, just pass the office
object to your Officeitem
like below
// Offices.vue
<template>
<div>
<ul :key="`${regionName}_${index}`" v-for="(region, regionName, index) in officesByRegion">
<h1>Region {{regionName}}</h1>
<OfficeItem v-for="office in region" :key="office.Region" :office="office"/>
</ul>
</div>
</template>
<script>
export default {
name: "Offices",
data() {
return {
offices: [
{ Region: "A" },
{ Region: "B" },
{ Region: "C" },
{ Region: "D" },
{ Region: "Z" },
{ Region: "A" },
{ Region: "B" },
{ Region: "A" },
{ Region: "B" },
{ Region: "A" },
{ Region: "Z" },
{ Region: "C" },
{ Region: "D" },
{ Region: "E" }
]
};
},
computed: {
officesByRegion() {
const obj = {};
this.offices.forEach(o => {
if (o.Region in obj) obj[o.Region].push(o);
else obj[o.Region] = [o];
});
return obj;
}
}
};
</script>
I Hope this helps. Or at least shine's some light on dynamic css class application. :-)
Upvotes: 0