gridsome / gridsome

⚡️ The Jamstack framework for Vue.js
https://gridsome.org
MIT License
8.55k stars 489 forks source link

Ability to query page_sections created in Forestry #1488

Open Stafie opened 3 years ago

Stafie commented 3 years ago

Description

This is probably not a bug, but more of a request for help as I've been trying to achieve it for the past two days without much hope. I am using Gridsome as the SSG for a marketing website, and is connected to Forestry from where I am creating/editing the pages. Each page is composed out of multiple components, that can differ (each component can contain different fields). For example, a page could be composed of: HeaderBlock, HeroBlock, RowBlock, TestimonialBlock, RowBlock, FaqBlock, etc. I have the data structure defined in forestry and it outputs .md files with front matter data when creating a page. An example of how the frontmatter for a page looks like is:

---
title: index
description: A clear and direct guide to writing
page_sections:
- template: hsds-row
  image_position: right
  title: You’re in control
  paragraph_text: <p>Paragraph text</p>
  title_eyebrow: Principle 1
  image: "/uploads/app-control-panel-desktop.gif"
- template: hsds-faq
  title: Frequently asked questions
  faqs:
  - question: Question 1?
    answer: Answer 1.
  - question: Question 2?
    answer: Answer 2.
- template: hsds-row
  image_position: left
  title_eyebrow: Principle 2
  title: Access to all your own information
  paragraph_text: "<p>Paragraph text</p>"
  image: "/uploads/access-to-data-desktop-2x.png"
---

Steps to reproduce

In my Vue page template file, I'm query-ing the data and want to query all the page_section blocks, with the corresponding fields for each. My first guess was to do:

<page-query>
query ($path:String!) {
  hsdsPage(path: $path) {
    title
    description
    page_sections
  }
}
</page-query>

But unfortunately it doesn't let me to do it, as with GraphQL, I need to explicitly define each field from the page_section that I want to query. The problem is that not all items in the page_sections have the same fields, so I would end with pulled entries that would have a lot of unnecessary blank fields.

Expected result

I would expect a way to query all the blocks with its corresponding fields. I am somehow sure that I am missing something as in Forestry documentation I have found examples that does exactly this for Hugo and Jekyll (but I cannot see how exactly they are pulling the data): https://forestry.io/docs/settings/fields/blocks/#hugo

Actual result

When I try to query the page_sections without indicating each field, I get a Field "page_sections" of type "[HsdsPage_PageSections]" must have a selection of subfields. Did you mean "page_sections { ... }"?" error

Environment

  npmPackages:
    @gridsome/plugin-sitemap: ^0.4.0 => 0.4.0
    @gridsome/source-filesystem: ^0.6.2 => 0.6.2
    @gridsome/transformer-remark: ^0.6.4 => 0.6.4
    gridsome: ^0.7.0 => 0.7.23
  npmGlobalPackages:
    @gridsome/cli: 0.3.4
amxmln commented 3 years ago

I’ve actually been in your situation before and the good news is that it’s definitely already possible. :blush: The bad news is that it’s not that easy. :sweat_smile:

You need to create a block Interface in GraphQL that will be common to all your blocks, then each of your Blocks can implement that interface and you’ll be able to query them via that interface.

Here’s a simple example:

In gridsome.server.js:

//  the rest of (api) => {} omitted for brevity
api.loadSource(({ addSchemaResolvers, addSchemaTypes, schema }) => {
  // create an explicit schema for each of your different Blocks here, they all have to implement the Block interface defined below!
  addSchemaTypes(`
    type TextBlock implements Block {
       template: String!
       heading: String
       body: String
    }

   type PageNode implements Node @infer {
     id: ID!
     page_sections: [Block] # note how we’re using the interface here
   }
  `);

  addSchemaTypes([
    schema.createInterfaceType({
      name: 'Block',
      fields: { // define fields that all Blocks must have here
        template: 'String!',
      },
      resolveType(obj) { // this is where the magic happens, here you map Forestry’s template to the actual Type so it can be queried
        if (obj.template === 'text') return 'TextBlock';
      },
    }),
  ]);
});

This is modelled in part after this and adapted for Gridsome, in that question you’ll find further reading materials on how this works with GraphQL.

With this Schema in place, you’ll be able to query your content sections as follows:

<page-query>
query($id: ID!) {
  page_sections: {
    template,
    ...on TextBlock {
      heading,
      body
    }
  }
}
</page-query>

Please note that this is untested code written from memory (so I hope I got everything right), but I’ve built multiple Forestry + Gridsome sites using this system, so while it’s a bit verbose, it definitely works. :blush: I hope it helps!

Stafie commented 3 years ago

hey @amxmln! Thanks for your reply! I will try to do it today and get back to you here! ❤️

Stafie commented 3 years ago

@amxmln Thank you so much for your detailed example, it is exactly what I needed and it's such an elegant solution!

Are you currently looking for projects to work on? We are currently migrating our marketing website from Rails to Forestry+Gridsome, and might need some extra hands, especially from someone who has experience with the stack.

amxmln commented 3 years ago

@Stafie I’m glad I could help out. :blush: Thank you for your offer, but unfortunately I’m busy with other projects for the foreseeable future. I wish you all the best for your migration though!