Writing an Archive Calendar, part 4b

Man, paging — the second bug I'd reported to myself in part 4 — was long-winded, mainly because I was trying to use the built-in paging system and spent far too much time tracing through Graffiti code in Reflector. In the end, I abandoned that line of attack, mainly because I think I'd been gaming GraffitiCMS by creating a "post" called archive, when it was really a list of posts, and the auto-generated default.aspx was all wrong for that purpose. Further investigation will wait for a rainy day.

Anyway, I decided to write the paging essentially from scratch using ideas I'd gathered from my spelunking through the Graffiti.Core.dll.

First off, there's this nice class called GraffitiContext. It's a hash map, a set of name/value pairs containing some well-defined ones. Since it's a hash map you can add your own if you want (and I will). It stays valid throughout the current ASP.NET cycle. One of the more interesting name/value pairs is the "request" key, whose value is the HTTP request that started this cycle off. Included in that is the QueryString property, which I'll be reading for the page number.

    private static GraffitiContext GetContext(PostCollection posts) {
      GraffitiContext gc = GraffitiContext.Current;
      HttpRequest request = (HttpRequest)gc["request"];
      if (request != null)
        gc.PageIndex = int.Parse(request.QueryString["p"] ?? "1");
      gc.PageSize = SiteSettings.PageSize;
      gc.TotalRecords = posts.Count;
      gc["where"] = archiveWhere;
      return gc;
    }

First I read the QueryString for the "p" parameter, the page number. If it's not there, I assume it's page 1. Next I load up the page size and the total number of records: these will be used by the page navigator in a moment. For fun, I set the "where" property to my own value (actually "archive").

Next up is another little helper method. This one uses the values in the current GraffitiContext to create a PostCollection containing the posts for that particular page. We'll be returning that page of posts to the view.

    private static PostCollection GetPageOfPosts(PostCollection posts, GraffitiContext gc) {
      int start = (gc.PageIndex - 1) * gc.PageSize;
      int end = start + gc.PageSize;
      if (end > gc.TotalRecords) end = gc.TotalRecords;

      PostCollection postsPage = new PostCollection();
      for (int i = start; i < end; i++) {
        postsPage.Add(posts[i]);
      }
      return postsPage;
    }

Nothing too difficult.

Now we can write the improved Chalk extensions to return a page of posts instead of the full list.

    public PostCollection GetPostsForMonth(string yearAsString, string monthAsString) {
      int year, month;
      GetDateParts(yearAsString, monthAsString, out year, out month);
      PostCollection posts = PostsReader.GetFilteredPostsForMonth(year, month);

      GraffitiContext gc = GetContext(posts);
      gc["title"] = "Archives for " + new DateTime(year, month, 1).ToString("MMMM yyyy") + " : " + SiteSettings.Get().Title;
      gc[archiveQuery] = string.Format("?year={0}&month={1}", year, month);

      return GetPageOfPosts(posts, gc);
    }

    public PostCollection GetPostsForDay(string yearAsString, string monthAsString, string dayAsString) {
      int year, month, day;
      GetDateParts(yearAsString, monthAsString, dayAsString, out year, out month, out day);
      PostCollection posts = PostsReader.GetFilteredPostsForDate(year, month, day);

      GraffitiContext gc = GetContext(posts);
      gc["title"] = "Archives for " + new DateTime(year, month, day).ToShortDateString() + " : " + SiteSettings.Get().Title;
      gc[archiveQuery] = string.Format("?year={0}&month={1}&day={2}", year, month, day);

      return GetPageOfPosts(posts, gc);
    }

They both work in the same way: validate the input, get the full list of filtered posts, get and set the context (we add a better title, and we add the current query string as a special property of the context), and then we call the method to return the correct page of posts. (You can tell I'm tired, by the way. I can't be bothered at the moment to work out how to extract out the duplication.)

That's it for the generation of the pages of posts, for both a given month and for a specific date; now onto the pager.

Unfortunately $macros.pager won't do here. The archive calendar is already adding query strings to the URL and the standard pager doesn't expect that. So I had to write a simple pager, based on the interface of the  normal one.

    public static string Pager(string cssClass, string prevText, string nextText) {
      GraffitiContext gc = GraffitiContext.Current;
      return Util.Pager(gc.PageIndex, gc.PageSize, gc.TotalRecords, cssClass, (string)gc[archiveQuery], prevText, nextText);
    }

The main difference is the extraction of the current query string from the context where I'd stored it in either GetPostsForMonth() or GetPostsForDay(). If there is a current query string, Util.Pager() will just tack the page parameter bit on to the end.

The only change I had to the archive.view file was to call my pager instead of $macros.pager.

        $JmbChalk.Pager("pager", "« older posts", "newer posts »")

And that's it. A paged archive list.

(Part 1 is here, part 2 here, part 3 here, part 4 here, part 4a here, part 4b here.)

Now playing:
Pet Shop Boys - The Boy Who Couldn't Keep His Clothes On
(from Further listening 1995-1997)

Loading similar posts...   Loading links to posts on similar topics...

1 Response

#1 Dew Drop – January 17, 2009 | Alvin Ashcraft's Morning Dew said...
17-Jan-09 7:26 PM

Pingback from Dew Drop – January 17, 2009 | Alvin Ashcraft's Morning Dew

Leave a response

Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.

  •  Emphasize with italics: surround word with underscores _emphasis_
  •  Emphasize strongly: surround word with double-asterisks **strong**
  •  Link: surround text with square brackets, url with parentheses [text](url)
  •  Inline code: surround text with backticks `IEnumerable`
  •  Unordered list: start each line with an asterisk, space * an item
  •  Ordered list: start each line with a digit, period, space 1. an item
  •  Insert code block: start each line with four spaces
  •  Insert blockquote: start each line with right-angle-bracket, space > Now is the time...
Preview of response