Quasar: [Request] Allow data table 'field' to come from deeper than top level of data object.

Created on 22 Aug 2017  Â·  14Comments  Â·  Source: quasarframework/quasar

Sometimes data coming from the server that you want to display as a data table is not flat.

data () {
 return {
   tableData: [
     { 
         id: 1,
         name: 'fred',
         boss: {
            id: 2,
            name: 'bossman'
         }
      },
 ...
]

Now, in a column configuration, there's no way to identify boss.name as the data for a column. i.e., something like,

id   name   bossName
--  -----   --------
1   fred     bossman

The work-arounds I can think of:

  1. Change the shape of what comes from the server (you might not have control of the server, so this one isn't always available).

  2. Flatten the data by using a computed property to transform the hierarchical structure to a flat one. This isn't so bad, but is probably not a great idea when the data set starts getting large.

import _ from 'lodash

tableData: function () {
     return _.map(this.employees, (employee) => {
       return {id: employee.id, name: employee.name, bossName: employee.boss.name}
     })
  }

This seems to work at first blush, but I'm not certain it would hold up in all the data table use-cases like changing the sort or applying filters.

Perhaps it could look like,

columns: [
   {
      label: 'bossName',
      field: 'boss.name',
...

I'd also be satisfied with a workaround that didn't involve duplication of the entire data source array. Perhaps one exists using a function in place of the field name, but I can't think of it.

Most helpful comment

I believe it should be:
field: row => row.boss.name,

This will return 'bossman' instead of 'fred'.

E.g.:

<template>
  <div>
    <q-table
    title="Title"
    :data="tableData"
    :columns="columns"
    row-key="id"
    dense
    >
    <q-tr slot="body" slot-scope="props" :props="props" class="cursor-pointer">
      <q-td v-for="col in props.cols" :key="col.name" :props="props">
       <a>{{ col.value.name }}</a>
      </q-td>
    </q-tr>
  </q-table>
</div>
</template>

<script>
export default {
  // name: 'ComponentName',
  data () {
    return {
      tableData: [
        {
          id: 1,
          nested: {
            id: 2,
            name: 'Name inside nested object'
          }
        }
      ],
      columns: [
        { name: 'nested', label: 'Nested objects', field: row => row.nested }
      ]
    }
  }
}
</script>

All 14 comments

Another workaround, even solution, perhaps...

  1. Use a transformer when fetching the data.

This came from the MAPT video course by Peter van Meijgaard
https://www.packtpub.com/mapt/video/web_development/9781788291613

class BaseTransformer {
  static fetchCollection (items) {
    return items.map(item => this.fetch(item))
  }

  static sendCollection (items) {
    return items.map(item => this.send(item))
  }
}
class EmployeeTransformer extends BaseTransformer {
  static fetch(employee) {
    return {
      id: employee.id,
      name: employee.name,
      bossId: employee.boss.id,
      bossName: employee.boss.name
    }
  }
  static send(employee) {
    // ...
  }
}

Then in the API call, filter the fetched data through the transformer before putting it into the vuex store.

// in vuex store actions...

export const list = ({commit}) => {
  proxy.list()
    .then((response) => {
      const data = {
        employees: EmployeeTransformer.fetchCollection(response)
      }
      commit(types.RECEIVE_EMPLOYEES, data)
    })
}

Will make this possible with the revamp of QDataTable

Another workaround, currently used by me:

  1. Render cell with a component.
<q-data-table :data="tableData" ...>
  <template slot="col-boss" scope="cell">
    <span>{{ cell.data.name }}</span>
  </template>
</q-data-table>

Closing as code is ready in v0.15 datatable revamp.

Just to update this so everyone is clear what now can be done.

Use obj => obj.some.nested.prop in your field property for the column. So, for Kirk's example above, it would be:

columns: [
   {
      label: 'bossName',
      name: 'The Boss'.
      field: row => row.boss.name, // corrected see below!
...

Scott

I believe it should be:
field: row => row.boss.name,

This will return 'bossman' instead of 'fred'.

E.g.:

<template>
  <div>
    <q-table
    title="Title"
    :data="tableData"
    :columns="columns"
    row-key="id"
    dense
    >
    <q-tr slot="body" slot-scope="props" :props="props" class="cursor-pointer">
      <q-td v-for="col in props.cols" :key="col.name" :props="props">
       <a>{{ col.value.name }}</a>
      </q-td>
    </q-tr>
  </q-table>
</div>
</template>

<script>
export default {
  // name: 'ComponentName',
  data () {
    return {
      tableData: [
        {
          id: 1,
          nested: {
            id: 2,
            name: 'Name inside nested object'
          }
        }
      ],
      columns: [
        { name: 'nested', label: 'Nested objects', field: row => row.nested }
      ]
    }
  }
}
</script>

@stefanvanherwijnen - Correct! I was testing to see if anyone was awake! You passed! LOL! 😄

Just kidding. 😉

Here is a fiddle to show the nested data and getting values out of it correctly.

https://jsfiddle.net/smolinari/ednwy63t/

Scott

Any clue how I can set the nested property dynamically?
If I set:
field: row => row[variable]
The field variable is set to:
function field(row) { return row[variable]
Where as it should be the value (string) of the variable (so if variable is 'boss', it should be return row['boss']).

I don't think that is possible. Maybe someone with more knowledge might know better.

My suggestion would be to set up a generic data structure and remap your data to fit it.

Scott

Thanks.
You are right, it seems to be impossible without using eval() or new Function().
What works is this:
var func = new Function('return function field(row){ return row[\'' + variable + '\']}')
field: func()

However, ESLint tells you not to use new Function so you have to set 'no-new-func': 0.

... I have just tried setting field: variable again and it now seems to work fine :sob: .
I think the key is to set the Object as the field variable and use custom QTr and QTd to extract the values out of the nested object.

I ended up getting following error when I use field: row => row.createdBy.email

My code looks like as below

columns: [
   {
      label: 'createdBy',
      name: 'The Boss'.
      field:  row =>row.createdBy.email  

My data comes dynamically from DB and it is getting the data correctly.

Please help with this.

@sachingk This sounds like a standard vue iteration warning -- what are you using for the :key in the v-for loop that renders your columns?

@sachingk Based on the vue warning, your email is either object or array

You are right.

I fixed my for loop. Now it works fine.

On Mon, 20 Jul, 2020, 6:06 AM Kirk Stork, notifications@github.com wrote:

@sachingk https://github.com/sachingk This sounds like a standard vue
iteration warning -- what are you using for the :key in the v-for loop that
defines your columns?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/quasarframework/quasar/issues/821#issuecomment-660737269,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AAH75R3NZNQBVHBSMYU3YMTR4OGQXANCNFSM4DX3VI4A
.

Was this page helpful?
0 / 5 - 0 ratings