0

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

11
  • You want your original Array to be chunked into Arrays of office by location? Commented Jun 12, 2019 at 7:26
  • Or for your offices to be sorted by region but in alphabetical order? Commented Jun 12, 2019 at 7:28
  • I want them to be sorted by region, and then based on the region, placed in the corresponding div. it doesn't have to be alphabetical order, and I'm not sure if making separate arrays would overcomplicate it Commented Jun 12, 2019 at 7:29
  • At the moment none of these items have the same Region ? Commented Jun 12, 2019 at 7:32
  • Oh i see, you want to treat certain offices differently based on their region. Commented Jun 12, 2019 at 7:33

2 Answers 2

1

[...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>

Sign up to request clarification or add additional context in comments.

3 Comments

I'm not familiar with the v-html and v-text as much. how can I get it to display without actually displaying the literal json object?
I added a version with your markup. v-text and v-html simply set the contents of an element (as text or as html) to the provided value. As a side note, you're doing :class="office.region". This will output class="Europe, Middle East and Africa". Are you sure you want classes with commas and spaces on your elements? That basically results in multiple classes. I strongly advise against it, on general coding design principles.
@Holly, also note I'm using a method to list all objects properties in the second example: Object.keys(obj).
0

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>
Alternatively, have a switch statement that takes the 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. :-)

6 Comments

From question: I need to sort the office items into separate divs based on the value of "Region". Perhaps you shouldn't assume all regions have 1 office. The question does say: "dynamic array". Which is why you should expect it to change. The dataset is just an example.
To be honest i've struggled to figure out exactly what the OP is needing. It looks in the example that Holly needs to have a region-specific CSS class added to each iteration - not have the Array sorted into office-by-region and then rendered.
I think it's simple. We're not here to help Holly. We're here to help anyone who might come here looking for a solution for a similar case. That simplifies things a bit, wouldn't you agree?
I think it's simple. We're not here to help Holly. We're here to help anyone who might come here looking for a solution for a similar case. That simplifies things a bit, wouldn't you agree?
@FrancisLeigh Sorry if that wasn't clear. I thought it was. Like I said, I just need to take an office object, find it's region, then append or place in a div corresponding to that region. the data here is hardcoded but will be from an api with many more offices. the divs will have one but most likely more than one office. That's why I thought I needed to loop through each office object to find it's region within a loop that goes through the array of office objects
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.