Assemble - How do I limit a collect to display X posts?

540 Views Asked by At

Details:

Assemble: 0.4.4

Grunt: 0.4.1

Question:

I'm designing a blog where I want to put 5 of the most recent posts on the front page. I've created a collection for my posts based on keywords:

assemble: {
  options: {
    flatten: false,
    partials: '<%= build.src %>/_partials/*.hbs',
    layoutdir: '<%= build.src %>/_layouts',
    data: ['<%= build.src %>/_data/*.{json,yml}', 'package.json'],
    assets: '<%= build.out %>/',
    helpers: [ 'helper-moment','<%= build.src %>/helpers/helper-*.js'],
    collections: [
    { name: 'keywords', inflection: 'keyword' }
    ]

  },

YAML front matter on the various posts look similar to this:

--

layout: default.hbs

title: <%= site.title %>

description: "Adult Redeploy All Sites Summit 2015"

dateCreated: 06-23-2014

slug: "Welcome"

breadCrumbs: false

posted: 01-12-2014

keywords:

  • news

navSort: 100

--

My code to display the titles and summaries is this:

<div>
  {{#each keywords}}
   {{#is keyword "news"}}
   {{#withSort pages "data.posted" dir="desc"}}
    <div>
     <h2><a href="/{{relativeLink}}">{{data.title}}</a></h2>
      <p>{{formatDate data.posted "%F"}}</p>
    <div>
        {{#markdown}}{{data.summary}}{{/markdown}}
    </div>
    <p><a href="/{{relativeLink}}">more...</a></p>
  </div>
  {{/withSort}}
  {{/is}}
  {{/each}}
 </div>

This works. It displays all the blogs no problem. But I want to limit to 5 -- the five most recent.

I've looked at this issue:

https://github.com/assemble/assemble/issues/463

But I'm not sure how to incorporate it into the example above. Is there a way to limit the pages #withSort?

Confused.

4

There are 4 best solutions below

0
On

There's good news and bad news. The bad news is that I don't believe there is a built-in helper that both sorts and limits the pages collection, nor can you piece two of them together in a pipeline.

Edited: I was wrong, there may be a built-in way combining collection sorting and the withFirst helper. I'll make a separate answer.

The good news is that you can write your own custom Handlebars Helper to do this. I wrote a sample helper below based on the withSort Helper. You would use it like this:

<div>
  {{#each keywords}}
    {{#is keyword "news"}}
      {{#withSortLimit pages sortBy="data.posted" dir="desc" limit=5}}
        <div>
          <h2><a href="/{{relativeLink}}">{{data.title}}</a></h2>
          <p>{{formatDate data.posted "%F"}}</p>
          <div>
          {{#markdown}}{{data.summary}}{{/markdown}}
          </div>
          <p><a href="/{{relativeLink}}">more...</a></p>
        </div>
      {{/withSortLimit}}
    {{/is}}
  {{/each}}
</div>

withSortLimit.js Here is the no-frills source to the withSortLimit helper. You will need to register this in your Gruntfile's Assemble configuration as described in the options.helpers docs.

/*
 * withSortLimit Handlebars Helper for Assemble
 * Sample usage:
 *   {{#withSortLimit pages sortBy="data.posted" dir="desc" limit=5}}
 *     <li>{{formatDate data.posted "%F"}}: {{data.title}}</li>
 *   {{/withSortLimit}}
 */
(function() {

    var _ = require("lodash");

    function getPropertyFromSpec(obj, propertySpec) {
        var properties = propertySpec.split('.');
        var resultObj = obj;
        _.each(properties, function (property) {
            if (resultObj[property]) {
                resultObj = resultObj[property];
            }
        });
        return resultObj;
    }

    module.exports.register = function(Handlebars, options) {
        Handlebars.registerHelper("withSortLimit", function(collection, options) {
            var result = "";
            var selectedPages = collection;

            // Sorting
            var sortProperty = options.hash.sortBy || "basename";
            selectedPages = _.sortBy(collection, function (item) {
                return getPropertyFromSpec(item, sortProperty);
            });
            if (options.hash.dir && options.hash.dir === "desc") {
                selectedPages = selectedPages.reverse();
            }

            // Limit
            if (options.hash.limit && options.hash.limit > 0) {
                selectedPages = _.first(selectedPages, options.hash.limit);
            }

            // Rendering
            _.each(selectedPages, function (page, index, pages) {
                result += options.fn(page);
            });

            return result;
        });
    };

}).call(this);
0
On

Well, I finally got this working, but it's probably not the best way to go about it. In fact, it's brute-force all the way. But I'm going to post what finally worked.

Here's the setup -- slightly modified from my original question.

I wanted to create a partial -- sidebar.hbs -- that lists the 5 most recent posts. I then wanted to call that partial inside my "normal" pages when I wanted a sidebar with recent content. Okay, so here's the setup.

My assemble routine in grunt.js:

grunt.js

assemble: {
  options: {
    flatten: false,
    partials: '<%= build.src %>/_partials/*.hbs',
    layoutdir: '<%= build.src %>/_layouts',
    data: ['<%= build.src %>/_data/*.{json,yml}', 'package.json'],
    assets: '<%= build.out %>/',
    helpers: [ 'helper-moment','<%= build.src %>/helpers/helper-*.js'],
    collections: [
    {  name: 'keywords',
      inflection: 'keyword',
      sortby: 'posted',
      sortorder: 'desc',
    }
    ]

  },

I then realized -- much like you kindly responded above -- that I can loop through the collection:

sidebar.hbs (not working -- {{relativeLink}} isn't defined -- but sorted!)

     {{#each keywords}}
        {{#is keyword "news"}}
        {{#withFirst pages 3}}
           <h5 style="padding-left: 7px; padding: 0; margin: 0;">
<a href="{{relativeLink}}">{{data.slug}}</a></h5>
       {{/withFirst}}
  {{/is}}
  {{/each}}
          
     

Except that -- and here's the rub: {{relativeLink}} is not properly set here. It returns nothing -- it's blank. I suspect I'm confusing contexts by calling the sidebar.hbs partial from a template page. I'm not sure, but I do know that the code below works as a partial -- and returns the proper {{relativeLink}}:

sidebar.hbs (working, no sorting -- simply displays all pages in site)

{{#each pages}}


{{data.slug}} -- {{relativeLink}}
<br/>

{{/each}}

The problem there, of course, is that it returns all the pages -- and they're not sorted.

So, in order to get my sidebar partial working -- and returning the proper links no matter where we are in the site hierarchy -- I created my links like this (and I'm embarrassed to post this -- but it works). I used the 'page.src' variable in the #withFirst loop. This works for what I want but seems awkward:

sidebar.hbs (working, sorting, but doesn't seem the right way to do this):

    {{#each keywords}}
  {{#is keyword "news"}}
  {{#withFirst pages 4}}

  <div class="myArticle">
    <article class="recent-posts" >


      <h5 style="padding-left: 7px; padding: 0; margin: 0;"><a href="{{data.siteFolder}}{{constructPostLinks src ext}}">{{data.slug}}</a></h5>
    <p>

      <span >
        {{moment data.posted format ="DD MMM YYYY"}}

      </span>



    </p>



  </article>
</div>

  {{/withFirst}}
  {{/is}}
  {{/each}}

What I'm essentially do is calling a helper -- 'constructPostLinks' and hacking together a URL from the site's site's folder on my website (again, defined in data.yml), and the 'src' page variable (which I pass to my custom handlebars template). The 'src' page variable is not anything I need -- it usually looks like /src/about/index.hbs or something like that -- so I strip off the 'src' and replace the '.hbs' with 'ext' (in this case, '.html').

Here's the handlebars helper:

helper-constructPostLinks.js

module.exports.register = function (Handlebars)  {
  Handlebars.registerHelper('constructPostLinks', function(page,ext) {
    pageLink = page.replace('src/','').replace('.hbs',ext);
    return new Handlebars.SafeString(pageLink);
  });
};

I don't know. This seems like an awfully clumsy way to get the links generated, but it works. I can write a page -- index.hbs -- and then include the sidebar partial {{>sidebar}} -- and then everything is pulled together in the default page template (page.hbs) where the links are properly generated for the most recently posted articles. It works. But I wish the collection sort routine included the proper {{relativeLinks}}.

As I say, I'm sure I'm doing something wrong and confusing contexts -- but at least I've got it going.

1
On

It should be possible to accomplish your goal with a combination of the following:

  1. Specify custom sorting rules for your keywords collection in the Gruntfile.
  2. Use the {{#withFirst pages 5}} helper to restrict the now-sorted list to your first five posts.

Gruntfile

assemble: {
  options: {
    flatten: false,
    partials: '<%= build.src %>/_partials/*.hbs',
    layoutdir: '<%= build.src %>/_layouts',
    data: ['<%= build.src %>/_data/*.{json,yml}', 'package.json'],
    assets: '<%= build.out %>/',
    helpers: [ 'helper-moment','<%= build.src %>/helpers/helper-*.js'],
    collections: [
      { name: 'keywords', inflection: 'keyword', sortby: 'posted', sortorder: 'desc' }
    ]
  },

Page Template

<div>
  {{#each keywords}}
    {{#is keyword "news"}}
      {{#withFirst pages 5}}
        <div>
          <h2><a href="/{{relativeLink}}">{{data.title}}</a></h2>
          <p>{{formatDate data.posted "%F"}}</p>
          <div>
          {{#markdown}}{{data.summary}}{{/markdown}}
          </div>
          <p><a href="/{{relativeLink}}">more...</a></p>
        </div>
      {{/withFirst}}
    {{/is}}
  {{/each}}
</div>
0
On

Had the same problem and ended up creating a helper for it.

var _ = require('underscore');

var helpers = {
    latest: function(array, amount, fn) {
        var buffer = "";

        _.chain(array)
            .filter(function(i) {
                return i.data.date;
            })
            .sortBy(function(i) {
                return i.data.date;
            })
            .reverse()
            .first(amount)
            .forEach(function(i) {
                buffer += fn.fn(i);
            });

        return buffer;
    },

};

module.exports.register = function(Handlebars, options) {
    options = options || {};

    for (var helper in helpers) {
        Handlebars.registerHelper(helper, helpers[helper]);
    }
    return this;
};

Add it somewhere along assemble.helpers search path. Make sure you have underscore dependency installed

npm install underscore --save-dev --save-exact

Then you can use the helper like this

  <ul>
    {{#latest pages 5}}
      <li><a href="{{relativeLink}}">{{data.title}}</a></li>
    {{/latest}}
  </ul>