Aurore
Aurore

Reputation: 746

Fetch all nested content files and group them by directories in Nuxt content

I'm having this list of blogposts called articles that I call from the content folder.

  async asyncData ({ $content }) {
    const articles = await $content('', { deep: true })
      //  .where({ cat: 'A' })
      .only(['title', 'description', 'img', 'slug', 'cat'])
      .sortBy('createdAt', 'asc')
      .fetch()

    return { articles }
  }

This fetches all of my articles and returns a list of them.

enter image description here

Now, I would like to populate my different categories in my website following this structure :

<template>
  <div class="container">
    <div v-for="article in articles" :key="article">
      {{ article.title }}
    </div>
    <pre> {{ articles }}</pre>

    <div v-for="accordion in accordions" :key="accordion.title" class="l">
      <Item>
        <template #title>
          {{ accordion.title }}
        </template>

        <template #content>
          <div> {{ accordion.text }}</div>
        </template>
      </Item>
    </div>
    <!-- here goes R -->
    <div class="r" />
  </div>
</template>

<script>
import Item from '../components/List-item.vue'
export default {
  components: { Item },
  async asyncData ({ $content }) {
    const articles = await $content('', { deep: true })
      //  .where({ cat: 'A' })
      .only(['title', 'description', 'img', 'slug', 'cat'])
      .sortBy('createdAt', 'asc')
      .fetch()

    return { articles }
  },
  data () {
    return {
      accordions: [
        {
          title: 'A',
          text: 'Projects from content/A'
        },
        {
          title: 'B',
          text: 'Projects from content/B'
        },
        {
          title: 'C',
          text: 'Projects from content/C'
        }
      ]
}
</script>

It's using slots from a componenent :

<template>
  <div class="wrapper">
    <div
      :class="{ active: isActive }"
      @click="toggle"
    >
      <a class="title">
        <slot name="title" />
      </a>

      <div v-show="show" :class="{ active: isActive }" class="content">
        <slot name="content" />
      </div>
    </div>
  </div>
</template>

I've nested my array into a v-for, but I don't know how to dynamically group that array by URL or by category.

Upvotes: 2

Views: 1475

Answers (3)

kissu
kissu

Reputation: 46610

This is how I would achieve that while keeping it fully dynamic and less error prone.

<template>
  <div class="container">
    <div v-for="(filteredArticles, categoryKey) in groupedCategories" :key="categoryKey">
      <h2>{{ categoryKey }}</h2>
      <list-item v-for="article in filteredArticles" :key="article.slug">
        <template #title>
          {{ article.title }}
        </template>
        <template #content>
          <div> {{ article.description }}</div>
        </template>
        <br>
      </list-item>
      <hr>
    </div>
  </div>
</template>

<script>
export default {
  async asyncData ({ $content }) {
    const articles = await $content('', { deep: true })
      .only(['title', 'description', 'img', 'slug', 'cat', 'dir'])
      .sortBy('createdAt', 'asc')
      .fetch()

    return { articles }
  },
  computed: {
    groupedCategories () {
      return this.articles.reduce((finalObject, obj) => {
        const directory = obj.dir
        finalObject[directory] ?? (finalObject[directory] = [])
        finalObject[directory].push(obj)
        return finalObject
      }, {})
    }
  }
}
</script>

The .only(['dir']) gives us all the directories aka /A, /B etc, so that it's fully dynamic and not relying on OP's needing to remember to put a cat in the .md file.
It is generated for any kind of content as shown here and avoids us some ugly and complex regex subgroups.

Then we're using a reduce to properly group all the articles depending on their respective directories, filtering on dir as explained before.

The Logical Nullish Assignment operator in the documentation didn't worked for me for some reason, hence I replaced it with it's alternative aka finalObject[directory] ?? (finalObject[directory] = []) to basically set a new array in case of a non-existing key.
The idea is to get an object with a key for each directory, itself containing an array of all the articles.

This is how it looks locally + Vue devtools.

enter image description here

A github repo can be found here: https://github.com/kissu/AppPortfolio
Useful since it's a bit cumbersome to mock all kind of .md files.
A deployed version can be found here: https://papaya-truffle-df823c.netlify.app/

Upvotes: 2

danh
danh

Reputation: 62686

If the intent is to see the list of articles queried in an accordion that divides them by "cat", you can augment the accordion data structure in data to include the articles found in the query...

async asyncData ({ $content }) {
  const articles = await $content('', { deep: true })
    .only(['title', 'description', 'img', 'slug', 'cat'])
    .sortBy('createdAt', 'asc')
    .fetch()

  // set accordions as an array of grouped articles
  const groups = articles.reduce((acc, article) => {
    if (!acc[article.cat]) acc[article.cat] = { 
      title: article.cat,
      text: `Articles about ${article.cat}`,
      articles: []  // <-- gather the articles for this cat here
    };
    acc[article.cat].articles.push(article);
    return acc;
  }, {});
  this.accordions = Object.values(groups);
}

The markup should iterate the accordion in data (which are really categories), and then nest iteration of the articles in each category...

<div v-for="accordion in accordions" :key="accordion.title" class="l">
  <Item>
    <template #title>
      {{ accordion.title }}
    </template>

    <template #content>
      <div> {{ accordion.text }}</div>
      <ul>
        <li v-for="(article, i) in accordion.articles" :key="i">
          {{article.title}}
        </li>
      </ul>
    </template>
  </Item>
</div>

Upvotes: 3

thebyteland
thebyteland

Reputation: 376

You can use conditional queries with @nuxt-content module:

{
  async asyncData ({ $content }) {
    const articlesC = await $content('', { deep: true })
      .where({ cat: { $contains: 'C' } })
      // Or .where({ cat: { $eq: 'C' } })
      .only(['title', 'description', 'img', 'slug', 'cat'])
      .sortBy('createdAt', 'asc')
      .fetch()

    return { articles }
  }
}

And the create an array for each category.

If you do not want to create a query for each category you can use the Array.filter (or Array.reduce) method to group your articles big query.

Upvotes: 0

Related Questions