Search code examples
javascriptarraysvue.jshtml-table

Vue V-for in tables


I am trying to create tables using the v-for directive, but I wanted to know if there is any way to do some conditionals inside the individual tables based on a certain value. I am including a generic version of what I have as my data and what I am trying to produce.

Data Example (this is what I am pulling in from the API call):

MainOrg SubOrgId SubOrgName SubOrgState TotalOrgs
10110 101101 Main Office AK 26
10110 101102 Branch Office AK 4
10110 101102 Sat Office AK 2
10111 101111 Main Office FL 26
10111 101112 Branch Office FL 4
10111 101112 Sat Office FL 2

I am trying to loop through the "MainOrg" column and create a new table for each unique one, then have the data that corresponds to that "MainOrg" in the output table. The "MainOrg" would become the title for the table.

The output I am trying to get is as follows:

10110

SubOrgId SubOrgName SubOrgState TotalOrgs
101101 Main Office AK 26
101102 Branch Office AK 4
101102 Sat Office AK 2

10111

SubOrgId SubOrgName SubOrgState TotalOrgs
101111 Main Office FL 26
101112 Branch Office FL 4
101112 Sat Office FL 2

I have been running into the following issues with the v-for and v-if directives:

**Duplicates main orgs due to it being based on index

<table v-for="(d, index) in data" :key="index">
  <thead>
    <tr>
      <th>{{ d.MainOrg }}</th>
    </tr>
  </thead>

I really want it to spit out a new table for each unique "MainOrg", then contextually look at my data to include the "SubOrg" data that matches it, per my desired result above. I have not been able to find the right combination of Vue Html and/or JavaScript that can create the desired result based on the need for the data to be separated into individual tables. Also, within the table elements, I am unsure of how to reference the index of the data for conditionals. For example, when I tried using v-if instead of v-for to create the tables by accessing a unique array of the MainOrgs, I did not know how to contextually tie the data together.

In non-programmer speak/pseudo code: Take the unique MainOrg values from data and create a new table for each MainOrg. Then, take the remaining columns/rows from data and add them to each MainOrg table where the row context (data.MainOrg) matches the table for that MainOrg.

Apologies for the long post, but any help is greatly appreciated.

Edit I am getting slightly different results from the two answers suggested as follows:

computed: {
  regions: ({ estabSearchResult }) =>
    estabSearchResult.reduce(
      (map, { region, ...rest }) => ({
        ...map,
        [region]: [...(map[region] ?? []), rest],
      }),
      {}
    ),
  },

Some data shown is modified for sensitivity Which gives me the following:

regions:Object
IN0110 - :Array[2]
IN0114 - :Array[1]
IN0115 - :Array[1]
IN0120 - :Array[1]
IN0130 - :Array[1]
IN0160 - :Array[1]
IN01BB - :Array[1]
IN28AO - :Array[1]
IN28BO - :Array[13]

The forEach() method(below)

if (this.estabSearchResult.length > 0) {
  const newObj = {}
  this.estabSearchResult.forEach(obj => {
    newObj[obj.region] ?
      newObj[obj.region].push(obj) : newObj[obj.region] = [obj]
  })
  this.estabRegionGroup = newObj
}

gives me the following:

estabRegionGroup:Object
IN0110 - :Array[3]
IN0114 - :Array[1]
IN0115 - :Array[1]
IN0120 - :Array[1]
IN0130 - :Array[1]
IN0160 - :Array[1]
IN01BB - :Array[1]
IN28AO - :Array[1]
IN28BO - :Array[13]

Notice the array size for IN0110. The forEach() gives me 3 objects in the array, where the reduce(map()) gives me only two. All other items/regions are the same and correct, only that first one is off. Any ideas? The results for IN0110 should have 3 objects in it.


Solution

  • Create a computed property to represent the new data structure

    computed: {
      regions: ({ estabSearchResult }) =>
        estabSearchResult.reduce(
          (map, { region, ...rest }) => ({
            ...map,
            [region]: [...(map[region] ?? []), rest],
          }),
          {} // don't forget to init with an empty object
        ),
    }
    

    This looks something like

    {
      "10110": [{ SubOrgId: 101101, ... }, ...],
      "10111": [{ SubOrgId: 101111, ... }, ...],
    }
    

    which you can then use in rendering

    // Just some fake, random data using your "region" values
    const fakeApi = {get:()=>new Promise(r=>setTimeout(r,1000,[{"region":"IN0110","foo":0.7051213449189915},{"region":"IN0110","foo":1.4213972602828675},{"region":"IN0110","foo":2.075397586536013},{"region":"IN0114","foo":0.7055750843380546},{"region":"IN0115","foo":0.5976362522109442},{"region":"IN0120","foo":0.6605446959279311},{"region":"IN0130","foo":0.7179704337235409},{"region":"IN0160","foo":0.19066097499077084},{"region":"IN01BB","foo":0.0019511615325726872},{"region":"IN28AO","foo":0.46443847116756487},{"region":"IN28BO","foo":0.41268230939585426},{"region":"IN28BO","foo":1.9572873014553014},{"region":"IN28BO","foo":2.610276341696757},{"region":"IN28BO","foo":3.7301898988777733},{"region":"IN28BO","foo":4.021495358709221},{"region":"IN28BO","foo":5.996280938563549},{"region":"IN28BO","foo":6.66092280472865},{"region":"IN28BO","foo":7.660937785660997},{"region":"IN28BO","foo":8.288195167562918},{"region":"IN28BO","foo":9.84101941796214},{"region":"IN28BO","foo":10.34450778678685},{"region":"IN28BO","foo":11.782972607317836},{"region":"IN28BO","foo":12.05067212727201}]))};
    
    new Vue({
      el: "#app",
      data: () => ({
        estabSearchResult: [],
      }),
      async created () {
        this.estabSearchResult = await fakeApi.get();
      },
      computed: {
        regions: ({ estabSearchResult }) =>
          estabSearchResult.reduce(
            (map, { region, ...rest }) => ({
              ...map,
              [region]: [...(map[region] ?? []), rest],
            }),
            {}
          ),
      }
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
    <div id="app">
      <p v-if="estabSearchResult.length === 0">Loading...</p>
      <div v-for="(orgs, region) in regions" :key="region">
        <p><strong>{{ region }} ({{ orgs.length }})</strong></p>
        <table border="1">
          <thead>
            <tr>
              <th>Foo</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(org, index) in orgs" :key="index">
              <td>{{ org.foo }}</td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>