Home > Enterprise >  How to render a table from a database in typescript
How to render a table from a database in typescript

Time:06-21

I'm a bit of a novice coder and I'm struggling to get some data to display in a table on a web page. This feels like it should be basic, but I just can't quite get the display right.

The data in the DB is in the following format, with each row containing data that will need to become a column heading and a row heading in the final rendered table.

Code,Ate,Round,Value,Active
ZOM3,Ears,16,18,True
ZOM4,Ears,16,22,True
ZOM4,Knees,16,24,True
ZOM4,Arms,16,90,True
ZOM4,Brains,16,940,True
ZOM4,Ears,17,29,True
ZOM4,Arms,17,114,True
ZOM4,Brains,17,746,True
ZOM4,Ears,18,23,True
ZOM4,Shins,18,1930,True
ZOM4,Arms,18,135,True
ZOM4,Brains,18,952,True
ZOM4,Ears,19,22,True
ZOM4,Shins,19,580,True
ZOM4,Knees,19,32,True
ZOM4,Arms,19,139,True
ZOM5,Shins,18,14,True

For character ZOM4, the way I need this to present on a web page is in a table like this:

16 17 18 19
Arms 90 114 135 139
Brains 940 746 952
Ears 22 29 23 22
Knees 24 32
Shins 1930 580

Sadly, my code does not. In the .vue file I have the following code, which ignores the null/missing Value values so that ZOM4 displays 1930 Shins in round 16.

const eats = computed(() => {
  if (!props.data) return {};

  const d = {} as { [key: string]: { rounds: { [key: string]: any } } };

  for (let x of props.data.roundlyData) {
    if (!d[x.ate]) {
      d[x.ate] = { rounds: {} };
    }

    d[x.ate].rounds[x.round] = x.value;
  }

  return d;
});

const rounds = computed(() => {
  const t = Object.keys(eats.value)[0];
  if (!t) return [];

  return Object.keys(eats.value[t].rounds);
});
</script>

<template>
  <table >    <!-- might need to sort the above data by ate and/or by round before presenting it. Test with Co. WPL -->
    <colgroup>
      <col style="width: 60px" />
    </colgroup>
    <thead>
      <tr v-if="rounds">
        <th></th>
        <th v-for="round of rounds" :key="round">{{ round }}</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(t, ate) of eats" :key="ate">
        <td>{{ ate }}</td>
        <td v-for="r in t.rounds" :key="r">{{ r }}</td>
      </tr>
    </tbody>
  </table>
</template>

This is driving me crazy. Can someone please help me get the table to display in a way that respects the nulls/missing data. Thanks

CodePudding user response:

Looks like your data is arms.rounds=[16:90,17:114,18:135,19:139] and shins.rounds=[18:1930,19:580].
In your Loop

 <td v-for="r in t.rounds" :key="r">{{ r }}</td> ....

you get just two entries for the shins, because you do not loop over [16,17,18,19] but the entries of rounds. So either ensure that the entries 16 and 17 are set to null in shins or loop over [16,17,18,19] and display the data accordingly if empty.

CodePudding user response:

Crunching data into tables is almost never basic. Here's how I would do it:

const { createApp } = Vue;

createApp({
  setup() {
    const initialData = `Code,Ate,Round,Value,Active
ZOM3,Ears,16,18,True
ZOM4,Ears,16,22,True
ZOM4,Knees,16,24,True
ZOM4,Arms,16,90,True
ZOM4,Brains,16,940,True
ZOM4,Ears,17,29,True
ZOM4,Arms,17,114,True
ZOM4,Brains,17,746,True
ZOM4,Ears,18,23,True
ZOM4,Shins,18,1930,True
ZOM4,Arms,18,135,True
ZOM4,Brains,18,952,True
ZOM4,Ears,19,22,True
ZOM4,Shins,19,580,True
ZOM4,Knees,19,32,True
ZOM4,Arms,19,139,True
ZOM5,Shins,18,14,True`;
    const isNumeric = (num) => !isNaN(num);
    const rawData = initialData
      .split('\n')
      .map(
        entry => entry
          .split(',')
          .map(s => s.trim())
          .map(n => isNumeric(n) ?
            Number(n) :
            ['True', 'False'].includes(n) ?
              n === 'True' :
              n
          )
      );
    const names = rawData.splice(0, 1)[0];
    const rounds = [...new Set(rawData.map(a => a[2]))];
    const entries = rawData.reduce((a, b) => [...a, Object.assign({},
      ...names.map(
        (h, i) => ({
          [h]: b[i]
        })
      )
    )], []).reduce((a, { Code, ...rest }) => {
      a[Code] = [...(a[Code] || []), rest];
      return a
    }, {});

    return {
      entries,
      rounds,
      getEntryRows: entry => [...new Set(entry.map(o => o.Ate))].sort(),
      getEntryCell: ({
        food,
        name,
        round
      }) => entries[name]
        .find(o => o.Ate === food && o.Round === round) 
        ?.Value || '---'
    }
  }
}).mount('#app')
body {
  font-family: sans-serif;
}

table {
  width: 100%;
  max-width: 800px;
  border-collapse: collapse;
}

th[colspan] {
  border-color: #ddd #fff;
  font-size: 1.5rem;
  padding: 2rem 0 1rem;
  font-weight: normal;
}

td,
th {
  padding: .5rem .75rem .25rem;
  text-align: left;
  border: 1px solid #ddd;
}

td:first-child {
  padding-left: 2rem;
}

.rounds th {
  width: 20%;
  background-color: #f8f9f9;
}

.row-name {
  display: flex;
  align-items: center;
}

.row-name:before,
.row-name:after {
  content: '';
  flex-grow: 1;
  margin: 0 1rem;
  height: 0;
  border-bottom: 1px solid #ddd;
}

.row-name:before {
  margin-left: 0;
}

.row-name:after {
  margin-right: 0;
  flex-grow: 2.4;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.2.37/vue.global.prod.min.js"></script>
<div id="app">
  <table>
    <tbody>
      <template v-for="(entry, name) in entries">
        <tr :key="`h-${name}`">
          <th :colspan="rounds.length   1">
            <div  v-text="name"></div>
          </th>
        </tr>
        <tr :key="`r-${name}`" >
          <th v-for="round in ['', ...rounds]" v-text="round"></th>
        </tr>
        <tr :key="`${row}-${name}`"
            v-for="food in getEntryRows(entry)">
          <td v-text="food"></td>
          <td v-for="round in rounds"
              :key="round"
              v-text="getEntryCell({food, name, round})"></td>
      </template>
  </table>
</div>

  • Related