Routing: displaying Tagged posts a "portfolio" page template

What version of Ghost are you using?
v4.3.3
How was Ghost installed and configured?
running local dev using ghost-CLI

Hi there,

I’m working on creating an music portfolio website theme for Ghost. I’m trying to use the ghost’s routing to provide data for all posts tagged ‘portfolio’, and display template html components for each post.

My problem is that I don’t understand how to access the data in my template. The docs mention a {{#tag}} helper, but this helper doesn’t seem to have its own entry in the docs, only {{#tags}} which I believe isn’t what is needed, since I’m trying to display the list of posts from within the tag API call.

here’s what my dev/setting/routes.yaml looks like :

Summary
routes:
  /portfolio/:
    template: portfolio
    data: tag.portfolio

collections:
  /:
    permalink: /{slug}/
    template: index

taxonomies:
  tag: /tag/{slug}/
  author: /author/{slug}/

I’m using the internal tag ‘#portfolio’ but I’ve renamed the slug appropriately, dropping the ‘hash-’ (see pic). I’m noticing that navigating to ‘localhost:2368/tag/portfolio/’ is giving a 404, but maybe that is normal behaviour for internal tags? When I am navigating to external tags, they are displaying fine.

Here’s my failed attempt to add the data to my template (portfolio.hbs). However, right now, none of the elements within the block are appearing in the DOM. I’m trying to use the {{#post}}{{/post}} helper, and i’ve also swapped this for {{#tag}}{{/tag}} with the same result. This code was previously working fine using a direct {{#get}} call, which is commented out here.

Summary
    {{!-- {{#get "posts" include="tags" filter="tag:portfolio" formats="plaintext"}}
        {{#foreach posts}} --}}
          
        {{#post}}
        {{#foreach posts}}
            <div class="portfolio-item" id={{slug}}>
                <img src={{feature_image}}
                    onload="var self=this;setTimeout(function(){self.style.opacity=1;},100);" style="opacity: 1;" />
                
                <div class="portfolio-item-detail">
                    <div class="portfolio-item-header">
                        <div class="portfolio-item-header-left split-block"></div>
                        <div class="portfolio-item-header-right split-block"></div>
                    </div>

                    <div class="portfolio-item-playlist">
                        <img class="portfolio-item-play-arrow" src="{{asset "ui/portfolio/play-arrow.svg"}}" />
                    </div>
                    <div class="portfolio-item-text">
                        {{plaintext}}
                    </div>
                </div>
            
            </div>
            {{/foreach}}
            {{/post}}
  
        
    {{!-- {{/get}}  --}}

    

    
</div>

Thanks in advance for the help! I’m sure there’s a simple way to make this work, but I’m not seeing how from the documentation I’ve been reading. Finally, I should mention that I have been restarting my local Ghost every time I change the routing file ;)
Fili

bumping this thread because we’re really stumped! Any help would be really appreciated!

ok found a solution! I used the helper {{log this}} in various parts of my template in order to output the different data contents that I was receiving through the data property in routes.yaml. I used {{log this}} to understand how the data was being passed into block scopes (ie. {{#posts}} {{#tag}}).

I solved this by using a ‘channel’ instead of a ‘route+data’ structure. It turns out the data property only returns a basic set of metadata for tags, and will not return the tagged posts themselves. ‘Channels’ (with a filter property) will return posts, but they need to be used in combination with a {{#foreach posts}} helper in the template. This happens simply by virtue of being a channel, so working with the ‘data:’ property is unnecessary.

Here’s the routes yaml file (with a few portfolio subroutes implemented - the ‘#’ means i’ve commented out the data property)

routes.yaml
routes:
  /portfolio/:
    controller: channel
    filter: tag:[portfolio]
    template: portfolio
    # data: tag.portfolio

  /portfolio/music/:
    controller: channel
    filter: tag:[portfolio-music]
    template: portfolio

  /portfolio/field-recording/:
    controller: channel
    filter: tag:[portfolio-fieldrecording]
    template: portfolio

collections:
  /:
    permalink: /{slug}/
    template: index

taxonomies:
  tag: /tag/{slug}/
  author: /author/{slug}/

and here is how to implement the code into the template (I’ve included the {{log this}} helper which I used for testing):

portfolio.hbs
            {{#foreach posts}}
            {{log this}}
               
                <div class="portfolio-item" id={{slug}}>
                    <img src={{feature_image}}
                        onload="var self=this;setTimeout(function(){self.style.opacity=1;},100);" style="opacity: 1;" />
                    
                    <div class="portfolio-item-detail">
                        <div class="portfolio-item-header">
                            <div class="portfolio-item-header-left split-block"></div>
                            <div class="portfolio-item-header-right split-block"></div>
                        </div>

                        <div class="portfolio-item-playlist">
                            <img class="portfolio-item-play-arrow" src="{{asset "ui/portfolio/play-arrow.svg"}}" />
                        </div>
                        <div class="portfolio-item-text">
                            
                        </div>
                    </div>
                
                </div>
            {{/foreach}}

The Ghost docs do kind of hint at this approach, but there are a lot of things missing for this to be actually understandable to someone who is new to the platform. A simple explanation could have saved users several days of facepalming my way though a black box problem. I totally understand that Ghost is a small non-for-profit, and I like the ghost platform a lot. So here’s some ‘tough love’ …

Instead of updating ghost with things like ‘dark mode’ and a dashboard with analytics, Ghost needs to invest in their documentation, and their way of explaining concepts to users.

The current docs don’t explain much. They usually say things like “here is how you get the data” and then stop there. They assume you already are an experienced developer, and then proceed to do things that make experienced developers cringe.
two examples which we noticed recently are:

  • building 401 errors into v4 release as a ‘feature’ :smiley:
  • including a superfluous controller property for routing with only one option: ‘channel’.

At present, this is a major obstacle to making the Ghost an accessible platform for developers, or content creators without coding experience.

Here’s what can be explained better in the Ghost routing docs (I’ll stick to things relating to this post, but there are many MANY similar issues like this in the docs more generally):

  • {{#tag}} helper - right now there is no entry explaining this in the docs. what it is, and how it works as a block scope. It’s mentioned offhand under the data property.
  • break up the routing patterns and provide a quick overview of each one. Then go into detail for each instance, explaining how each pattern works in detail, and how to actually implement them within templates (explain what helpers are needed!).
  • explain what data actually is. the routing page says ‘here is how you use the data property to get data’… and I wanted to know – what is data? what is it used for? can it serve up posts (apparently not)? The fact that data is explained at the top of the page makes us assume that it is used in combination with the other patterns (i.e. taxonomies / channels) as a way of fetching post content. These differences and use cases need to be better defined.

Sorry if this post seems kind of harsh. I’m sharing all this because I don’t want anyone else to have the same experience I did, and also because I like the core concepts and design of ghost, and I want it to succeed in becoming an accessible tool for users.

2 Likes