Accessing items of a collection from a custom panel in Directus

In a previous post I showed you how to create a custom panel for your insights on Directus. Since it was a simple example I used hardcode data for the chart. Of course in a real application you can’t use hardcode information even if you’re really fast updating the content.

TL;DR

If you are already a directus expert you can clone the full code of this post here

Running our dev command to compile our panel on every change

So let’s update our custom extension to use data from a directus collections instead just hardcode data. Before starting let’s run the dev command:

1
2
cd piechart-panel
npm run dev

Accessing the collection from a vue component

For this example I’m going to create a user collection with 3 fields: id, name and country.

directus user collection page

And now I’m going to import from a csv file with some data for our collection

directus user list

Let’s open our panel.vue file and add the code that allow fetching the data from the user collection.
Since our component is just part of a whole Vue application directus already include a provider for accessing the API. The provider is called api.

1
2
3
4
export default defineComponent({
inject: ["api"],
// rest of the component config.
});

now we have the api object in our instance, let’s add the code to make the http request and get the information from the user collection. We’re going to do this in the mounted function.

1
2
3
4
5
6
7
8
9
10
11
export default defineComponent({
inject: ["api"],
mounted() {
this.api
.get("/items/user?aggregate[count]=id&groupBy[]=country")
.then((res: any) => {
console.log("Response", res);
});
},
// rest of the component config.
});

directus already provide documentation for accessing the items of a collection. For our example the url have the segments:

  • /items - because we’re trying to get items of a collection
  • /user - because that’s the name of the collection (can be anything you want at the moment of creating it)
  • ?aggregate[count]=id&groupBy[]=country - since we need the total of users per country this is similar to writing a sql query like this:
1
select count(id) from users group by country;

You can learn more about Aggregation & Grouping filters in this link.

After all of this we’re just logging the response to the console so we can check the structure:

directus items response structure

ok since directus use axios under the hood it’s a very familiar structure (at least for me a former axios fanboy) we have a data object and inside of it a data array that includes all the items from the user colecction group by country.
Let’s back a little bit and remember what’s the data structure that chartjs needs:

1
2
3
4
5
6
7
8
9
10
11
chartData: {
// here we need to add all the countries
labels: [],
datasets: [
{
backgroundColor: ["#41B883", "#E46651", "#00D8FF"],
// here the total number of users per country
data: [],
},
],
},

In order to make chartjs happy we can iterate the results in the response and in each iteration we can push the name of the country to the labels array and then get the total users per country from the count object.

1
2
3
4
5
6
7
8
9
10
11
mounted() {
this.api
.get("/items/user?aggregate[count]=id&groupBy[]=country")
.then((res: any) => {
// in a real app of course don't use anys all over the place
res.data.data.forEach((user: any) => {
this.chartData.labels.push(user.country);
this.chartData.datasets[0].data.push(user.count.id);
});
});
},

we’re almost done, the last thing to do is deal with the async nature of the operation, at the beginning the component is not going to have any data until the api send a response, so let’s add a quick loading message while the request is in progress and only show the chart until the response is ready.
Our template is gonna change a bit:

1
2
3
4
5
6
<template>
<div class="text" :class="{ 'has-header': showHeader }">
<Pie v-if="showChart" :data="chartData" />
<p v-else="!showChart">Loading...</p>
</div>
</template>

We’re using a showChart variable that is going to start with a false value and once the request has finished we change the value to true.
Of course the showChart variable needs to be declared in our data inside the definition of the component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
export default defineComponent({
data() {
return {
// start in false so the loading message appears first
showChart: false,
chartData: {
labels: [],
datasets: [
{
backgroundColor: ["#41B883", "#E46651", "#00D8FF"],
data: [],
},
],
},
};
},
mounted() {
this.api
.get("/items/user?aggregate[count]=id&groupBy[]=country")
.then((res: any) => {
res.data.data.forEach((user: any) => {
this.chartData.labels.push(user.country);
this.chartData.datasets[0].data.push(user.count.id);
});
// after we now all the data structure is ready we change
// the value to true
this.showChart = true;
});
},
});

Ok we’re ready to give it a try, let’s restart our container

1
docker-compose restart

and now we can see the final result, our chartjs custom panel using information from a directus collection

custom directus panel using data from collection

That’s it! Pretty simple to interact with the API and get information from a collection. Here’s the full code for the panel.vue file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<template>
<div class="text" :class="{ 'has-header': showHeader }">
<Pie v-if="showChart" :data="chartData" />
<p v-else="!showChart">Loading...</p>
</div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
import { Pie } from "vue-chartjs";

ChartJS.register(ArcElement, Tooltip, Legend);

export default defineComponent({
data() {
return {
showChart: false,
chartData: {
labels: [],
datasets: [
{
backgroundColor: ["#41B883", "#E46651", "#00D8FF"],
data: [],
},
],
},
};
},
components: {
Pie,
},
props: {
showHeader: {
type: Boolean,
default: false,
},
text: {
type: String,
default: "",
},
},
inject: ["api"],
mounted() {
this.api
.get("/items/user?aggregate[count]=id&groupBy[]=country")
.then((res: any) => {
res.data.data.forEach((user: any) => {
this.chartData.labels.push(user.country);
this.chartData.datasets[0].data.push(user.count.id);
});
this.showChart = true;
});
},
});
</script>

<style scoped>
.text {
padding: 12px;
}

.text.has-header {
padding: 0 12px;
}
</style>

Would be great if the users of the admin could have the possibility of choosing a collection and not be restricted to just the user collection like in this demo, but that’s for a future post.

Full code on github

Hope this post help you!