Posts tagged with 'archive'

Archive Calendar for GraffitiCMS now resets stats

After a while of using my archive calendar, the statistics for page views were starting to get really skewed in favor of the archive "post". It finally got to the point where the graph that's displayed under the Reporting tab in the Graffiti control panel had a line for archive that was 5 times longer than the nearest "real" post. Since the information about the number of people using the archive system is not that interesting to me, it was time to do something about it.

A bit of research later led me to the PostStatistic class in Graffiti.Core, in particular the Destroy() method. Since I wanted to have the ability to turn on or off the resetting of the archive stats, I decided to make the reset a Chalk extension. This meant it would become a method in my JmbCalendarHelper extension:

    public static void ResetArchivePageStatistics() {
      GraffitiContext gc = GraffitiContext.Current;
      Post post = gc["post"] as Post;
      if ((post != null) && (string.Compare(post.Name, "archive", true)) == 0){
        PostStatistic.Destroy(PostStatistic.Columns.PostId, post.Id);
      }
    }

Note that I was very careful to ensure that the post was really the archive post. Paranoid, moi? I didn't want to inadvertently reset the stats for another of my posts by mistake.

After that I added this line to the top of my cd:archive.view file:

$JmbCalendarHelper.ResetArchivePageStatistics()

so that the stats for the archive post would be reset every time the Archive Calendar system was used. In essence, they would always be zero.

I've updated the Archive Calendar page on CodePlex with this new release (v1.01).

Album cover for The Best of The Monkees Now playing:
The Monkees - Daydream Believer
(from The Best of The Monkees)


Share it: Digg It!  StumbleUpon  Reddit  Del.icio.us  NewsVine  Furl  BlinkList  Ma.gnolia  Technorati

Archive Calendar for GraffitiCMS released

Well that didn't take too long. Just enough time to open up an account at CodePlex, start a new project, point TortoiseSVN at it, and upload. The biggest time sink was actually the readme file and deciding on the license (I went for the MIT license).

I've now published the Archive Calendar for GraffitiCMS on CodePlex. You can get it here. If you want the source code, you can download it from the project's repository.

Album cover for Indigo Now playing:
Matt Bianco - Wap Bam Boogie
(from Indigo)


Share it: Digg It!  StumbleUpon  Reddit  Del.icio.us  NewsVine  Furl  BlinkList  Ma.gnolia  Technorati

Making the Calendar into a Graffiti CMS Widget

Last time I kind of trailed off. The Archive Calendar was working fine as a Chalk extension and I had a business trip to make for a week and doing the work to change it into a widget wasn't at the top of my list.

Earlier this week, though, back safely in Colorado Springs, I spent a couple of hours extracting it all out as a joint widget/Chalk extension assembly, all ready for posting on CodePlex. Since this is the first widget I'd written, and since the official help page is a little, er, brief on the whys and hows, I thought I'd write it all up.

First off, like the official help says, to create a widget you "create a class which derives from Graffiti.Core.Widget and is marked as being Serializable." And then, "Add the WidgetInfo attribute and specify a uniqueid (Guid), a name, and a description."

  [Serializable]
  [WidgetInfo("D6704F1F-AA4B-4d94-838F-9F8E7306F5D9", "JmbCalendar", "Archive post calendar using caching")]
  public class JmbCalendar : Widget {
    //..
  }

The uniqueid parameter of the WidgetInfo attribute constructor merely has to be unique; it doesn't have to be a GUID per se. It's just easier to use a GUID to make sure of uniqueness. (Use Visual Studio's Create GUID option in the Tools menu.) The Serializable part is very important as we'll see later, and it's a shame that a couple of the examples on the help page have this attribute missing. So don't forget it.

Next up: "Implement the abstract methods of Widget (RenderData and Name)." RenderData is the method that will be called to generate the HTML string that will render the widget in the sidebar.

    public override string RenderData() {
      int year;
      int month;
      GetMonth(out year, out month);

      GraffitiCalendar calendar = new GraffitiCalendar(year, month, PrevMonthFormat, NextMonthFormat);
      return calendar.ToString();
    }

For me, since I'd written all the underlying code already it was pretty simple. However, the Chalk extension was able to get the year and month from its call in the view. The widget doesn't have that luxury, so I had to write a method called GetMonth() to get the year and moonth parameters form the query part of the HTTP request.

    private void GetMonth(out int year, out int month) {
      string yearAsString = string.Empty;
      string monthAsString = string.Empty;
      GraffitiContext gc = GraffitiContext.Current;
      HttpRequest request = (HttpRequest)gc.InternalGet("request");
      if (request != null) {
        yearAsString = request.QueryString["year"];
        monthAsString = request.QueryString["month"];
      }
      JmbCalendarHelper.GetDateParts(yearAsString, monthAsString, out year, out month);
    }

The JmbCalendarHelper class herre is replacing the JmbChalk Chalk extension from the previous articles.

Back to the RenderData method above, we'll look at the PrevMonthFormat and the NextMonthFormat parameters in a moment.

Where the Name property is used The other member we must override in our widget is the Name property. This property is only shown in the Graffiti Control panel.

    public override string Name {
      get { return Title + " (JmbCalendar)"; }
    }

Another good property to override is Title, and I reference it above. The widget ancestor class has a backing store for this property so we don't have to create such a field for our descendant. The title is what appears on the sidebar as a header for the widget.

    public override string Title {
      get { return base.Title; }
      set {
        if (string.IsNullOrEmpty(value))
          base.Title = defaultTitle;
        else
          base.Title = value;
      }
    }
The defaultTitle referenced here merely has the value "Archives".

As hinted at above, I have two other properties, one for the format string for the previous month link, and one for the next month link. Obviously for these I have to have a couple of backing store fields.

    public string PrevMonthFormat {
      get { return prevMonthFormat; }
      set {
        if (string.IsNullOrEmpty(value))
          prevMonthFormat = defaultPrevMonthFormat;
        else 
          prevMonthFormat = value;
      }
    }

    public string NextMonthFormat {
      get { return nextMonthFormat; }
      set {
        if (string.IsNullOrEmpty(value))
          nextMonthFormat = defaultNextMonthFormat;
        else
          nextMonthFormat = value;
      }
    }

So far, I have three properties for my widget: the title and the two format strings. If someone — oh, I don't know, but how about Graffiti itself — were to serialize an instance of this class those would be the values saved. Handy that, especially when you ask how to change them in the widget control panel.

This is the other part of a widget that isn't well served by the documentation: the UI needed to change the widget's values. I've already pointed out that Graffiti can save and load the widget (and its configuration) to and from its internal storage using the Serializable stuff, but what about creating a UI in the control panel for the end-user to modify the values?

There are three methods you need to override, and they're all inter-related. The first method allows you to create a name/value collection to hold the widget data (the properties). The second allows you to save the edited data back into your object after the user clicked Update. The final one is a method called by the control panel to set up the UI itself to allow the editing. The UI is very simple as I'm sure you are aware: a property editor type display with one field per line.

The first method, then, is DataAsNameValueCollection(). Yeah, I know, it just trips off the tongue. It creates a name/value pair collection of the property values we wish to have edited.

    protected override NameValueCollection DataAsNameValueCollection() {
      NameValueCollection nvc = base.DataAsNameValueCollection();
      nvc["PrevMonth"] = PrevMonthFormat;
      nvc["NextMonth"] = NextMonthFormat;
      return nvc;
    }

We call the base method so that it can create a new NameValueCollection and add the title field, and then we add our two other property values. The keys I use here (PrevMonth and NextMonth) will be used again in a moment. This method will be called before the UI is created.

The next method I'll talk about is the SetValues method. This one returns the edited values back to your object. It uses a NameValueCollection again.

    public override StatusType SetValues(HttpContext context, NameValueCollection nvc) {
      StatusType result = base.SetValues(context, nvc);
      if (result == StatusType.Success) {
        PrevMonthFormat = nvc["PrevMonth"].Trim();
        NextMonthFormat = nvc["NextMonth"].Trim();
      }
      return result;
    }

Again we call the ancestor so that the title gets set, and then we set our own properties with the changed values. For this method we have to return a status value, here being success (there's not a lot that can go wrong in this class). Notice that I use the same keys as before.

Finally the AddFormElements() method. This method builds up a FormElementCollection list of controls that will be used for editing. The order of the elements in the collection determine the order in which they're displayed.

    protected override FormElementCollection AddFormElements() {
      FormElementCollection fec = base.AddFormElements();
      fec.Add(new TextFormElement("PrevMonth", "HTML for previous month link", "The HTML for the previous month link. Use {0} for the month name."));
      fec.Add(new TextFormElement("NextMonth", "HTML for next month link", "The HTML for the next month link. Use {0} for the month name."));
      return fec;
    }

My needs are simple, so I instantiated and used two TextFormElements. Notice again the PrevMonth and NextMonth keys turning up: the form code will use these to match up the editor control to the relevant data in the name/value pair collection. The call to the base method will set up the editor for the title and, obviously, create the collection object.

There are other form controls you can use (or you can write your own):

  • the CheckFormElement is a standard checkbox;
  • the DateTimeFormElement is an editor for editing dates and times;
  • the FileFormElement is an editor for choosing a file, either from the files area on the server or a new one to upload;
  • the MessageElement is not an editor at all, but just a way to show a line of text in the form;
  • the ListFormElement is for displaying a list of possibilities (you add a set of ListItemFormElement items to the list, and the user chooses one of them);
  • the TextAreaFormElement is a simple editor for a block of text;
  • the WYWIWYGFormElement is an editor for editing HTML, that is, rich text;

After that, it's just a case of compiling and deploying.

I hope this article has helped you understand how the widget you build interacts with the widget control panel in Graffiti CMS, and also what you need to implement and why.

I'll be making some last minute changes, adding a readme page and then uploading it all to CodePlex over the next couple of days.

Cover for Cafe del Mar, Volume 10 Now playing:
Digital Analog Band - The Blues
(from Café del Mar, Vol. 10)


Share it: Digg It!  StumbleUpon  Reddit  Del.icio.us  NewsVine  Furl  BlinkList  Ma.gnolia  Technorati

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}&amp;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}&amp;month={1}&amp;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", "&laquo; older posts", "newer posts &raquo;")

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)


Share it: Digg It!  StumbleUpon  Reddit  Del.icio.us  NewsVine  Furl  BlinkList  Ma.gnolia  Technorati

Writing an Archive Calendar, part 4a

OK the first bug I reported to myself in the previous post was trivial to fix. I changed the PostReader class to add a couple of new methods GetFilteredPostsForMonth():

    public static PostCollection GetFilteredPostsForMonth(int year, int month) {
      return GetFilteredPostsForDate(year, month, 0);
    }

and GetFilteredPostsForDate():

    public static PostCollection GetFilteredPostsForDate(int year, int month, int day) {
      PostCollection posts = GetPostsForDate(year, month, day);
      PostCollection filteredPosts = new PostCollection();
      foreach (Post post in posts) {
        if (RolePermissionManager.GetPermissions(post.CategoryId, GraffitiUsers.Current).Read)
          filteredPosts.Add(post);
      }
      return filteredPosts;
    }

Notice that what gets cached is the complete set of posts for the period (it's done in GetPostsForDate()), not the filtered set.

And then I changed the Chalk-visible GetPostsForMonth() and the GetPostsForDay() methods to call these new internal methods instead.

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

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

Done.

Now, onto paging...

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

Now playing:
Pet Shop Boys - In the Night
(from Further listening 1984-1986)


Share it: Digg It!  StumbleUpon  Reddit  Del.icio.us  NewsVine  Furl  BlinkList  Ma.gnolia  Technorati

Writing an Archive Calendar, part 4

Not much more to discuss for phase 1 of the Archive Calendar since we've covered the code. This post is a kind of wrap up (although it does contain a couple of warnings at the end) and we'll talk about what you have to do in order for the calendar to actually work and display the archived posts.

First up: to display the calendar on the home page just enter this into your layout.view file (or the home.layout.view), essentially in one of the sidebars.

$JmbChalk.ShowCalendar()

and the calendar will display for the current month.

For my site, I use a special widget <div> class, and so the code ends up like this, where I display the calendar followed by the rest of the sidebar:

    <div class="widget">
        <h2>Archives</h2>
        #if($request.year)
            $JmbChalk.ShowCalendar($request.year, $request.month)
        #else
            $JmbChalk.ShowCalendar()
        #end
    </div>
    $macros.RightSideBar("%{beforeWidget='<div class=\"widget\">', afterWidget='</div>', beforeTitle='<h2>', afterTitle='</h2>'}")

That way the calendar looks just like a widget, even though it isn't. Ignore the $request stuff for now, I'm coming to it.

In order to display the pages the links in the calendar point to, you need to create an uncategorized post called "archive". There's no need to put anything in this post, you just have to get Graffiti to create the archive folder off the root (that's where uncategorized posts go) and put the standard default.aspx file in it. The reason we won't be putting anything in this post is that we are not going to let the standard post.view display it. Instead, we're going to create an archive.view, and because of the way that Graffiti works, it will use this view to display the uncategorized post called "archive".

If you remember, the links in the calendar create URLs that look like this for the month:

http://blog.boyet.com/blog/archive/?year=2009&month=1

and like this for an actual date:

http://blog.boyet.com/blog/archive/?year=2009&month=1&day=8

Notice the query parts of the URLs (that is, the bits after the question mark). Graffiti decodes these and makes them available to the view using the $request Chalk class. So we have access to $request.year, $request.month, and $request.day. If the query parameter is not present, the corresponding property of the $request class will be null and we can test for it using code like #if($request.year) for example. (This should be read as "if the year parameter on the request is present...") Looking above you can now see that if the URL contains a query parameter for the year, we generate the calendar for that year and month. (Really I should be testing for the month too, but it doesn't matter since the code takes care of the validation.)

Given all that, here's the archive.view file:

    <div id="blog">

        #if($request.day)
            <h1>Archives for $JmbChalk.GetDateDisplayName($request.year, $request.month, $request.day)</h1>
            #foreach($post in $JmbChalk.GetPostsForDay($request.year, $request.month, $request.day) )
                <div class="post" id="$post.id">
                    $macros.LoadThemeView("postheader.view")
                    <div class="text">
                        $post.Excerpt("<p>", "</p>" ,"Read more...", 300)
                        $macros.LoadThemeView("postfooter.view")
                    </div>

                </div><!--end of post -->

            #nodata
                <div class="post">
                    Sorry, there are no posts matching your request.
                </div>

            #end
        #else
            <h1>Archives - $JmbChalk.GetMonthDisplayName($request.year, $request.month)</h1>
            #foreach($post in $JmbChalk.GetPostsForMonth($request.year, $request.month) )
                <div class="post" id="$post.id">
                    $macros.LoadThemeView("postheader.view")
                    <div class="text">
                        $post.Excerpt("<p>", "</p>" ,"Read more...", 300)
                        $macros.LoadThemeView("postfooter.view")
                    </div>

                </div><!--end of post -->

            #nodata
                <div class="post">
                    Sorry, there are no posts matching your request.
                </div>

            #end
        #end

        $macros.Pager("pager", "&laquo; older posts", "newer posts &raquo;")

    </div><!--end of blog -->

    $macros.LoadThemeView("sidebarleft.view")
    $macros.LoadThemeView("sidebarright.view")

As you can see, the main condition is to test to see if there's a day parameter. If so, we assume it's a display for an actual date, if not, we assume that it's for a single month. We patch up the relevant bits in the page by using the helper functions I'd created in part 3.

And that is that: the current state of play. Except...

There's a subtle bug in my routines to get the list of posts. For me on my blog here, it doesn't matter in the slightest, but for other users of GraffitiCMS it may. GraffitiCMS gives you the option to create roles (and therefore users) who may not have permissions to see the posts in certain categories (by default, a role has global permissions, but you can restrict them). As written, my code does not take this security aspect into account: the user gets to see everything, no filtering at all. I'll revisit that in the near future.

Secondly, although I merrily put in the call to the $macros.pager method, it doesn't work. The view just displays all the posts. No paging, it's for wimps. As it happens, I'd mistaken how the pager operates and so I have some code changes to make for that to work properly and page the list of posts properly. Since the pager also uses the query syntax for the URL, it'll be an interesting investigation. (I may just ignore the built-in pager and do my own.)

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

Album cover for Riptide Now playing:
Palmer, Robert - Riptide
(from Riptide)



Share it: Digg It!  StumbleUpon  Reddit  Del.icio.us  NewsVine  Furl  BlinkList  Ma.gnolia  Technorati

Writing an Archive Calendar, part 3

We're now at the coding climax of this series, writing the code that will actually render the calendar you see on the right.

I started off with a new class, the GraffitiCalendar (the previous calendar class, HtmlCalendar, merely rendered a read-only calendar as a standard HTML table). GraffitiCalendar is responsible for instantiating an HtmlCalendar, wiring up its two delegate properties — those responsible for generating the <a> links — and then letting it rip to produce the full "live" HTML.

  public class GraffitiCalendar {
    private int year;
    private int month;
    private PostCollection posts;

    public GraffitiCalendar(int year, int month) {
      this.year = year;
      this.month = month;
      this.posts = PostsReader.GetPostsForMonth(year, month);
    }

    public override string ToString() {
      HtmlCalendar calendar = new HtmlCalendar(year, month);
      calendar.GenerateMonthLink = GenerateMonthLink;
      calendar.GenerateDayLink = GenerateDayLink;
      return calendar.Render();
    }

    private string GetArchivePath() {
      string s = new Macros().Link("~/archive");
      if (!s.EndsWith("/"))
        s += "/";
      return s;
    }

    private string GetArchivePathForMonth(int year, int month) {
      return string.Format("{0}?year={1}&amp;month={2}", GetArchivePath(), year, month);
    }

    private string GetArchivePathForDay(int day) {
      return string.Format("{0}?year={1}&amp;month={2}&amp;day={3}", GetArchivePath(), year, month, day);
    }

    private int GetPostCount(DateTime date) {
      int count = 0;
      foreach (Post post in posts) {
        // note: early return possible because posts are ordered
        // in descending order by publish date
        if (post.Published.Date == date)
          count++;
        else if (post.Published.Date < date)
          return count;
      }
      return count;
    }

    public string GetFullMonthName(int month) {
      return new DateTime(2008, month, 1).ToString("MMMM");
    }

    public string GetAbbrMonthName(int month) {
      return new DateTime(2008, month, 1).ToString("MMM");
    }
  }

The constructor accepts the year and month for the calendar to be generated, and it stores these internally before calling PostReader to get the posts for that particular month. The ToString() method is the one that creates the HTML string by working as described above. The rest of the class shown here is merely a set of helper methods for the two delegates.

The GetArchivePath() method uses Graffiti's Macros helper class (yes, it's the same one you use in your Chalk code in your views) to convert a relative path to the archive folder into an absolute path. The two GetArchivePathXxx() methods construct paths for the month and single date archives that have the format:

http://blog.boyet.com/blog/archive/?year=2009&month=1

and

http://blog.boyet.com/blog/archive/?year=2009&month=1&day=8

We'll be seeing how to "get" at these year/month/day values later on. Note though that the links generated use &amp; for the ampersand character. Too many times developers use literal ampersands in their URLs in <a> links: this is invalid.

The two delegate methods are perhaps the most long winded methods of the whole class, but in reality, because of the work we've already done, they're trivial to write.

    public string GenerateDayLink(object sender, DayLinkEventArgs e) {
      DateTime date = new DateTime(year, month, e.Day);
      int count = GetPostCount(date);
      if (count == 0)
        return string.Empty;
      if (count == 1)
        return string.Format("<a href='{0}' title='View only post for {2}'>{1}</a>",
            GetArchivePathForDay(e.Day), e.Day, date.ToString("d-MMM-yyyy"));
      if (count == 2)
        return string.Format("<a href='{0}' title='View both posts for {2}'>{1}</a>",
            GetArchivePathForDay(e.Day), e.Day, date.ToString("d-MMM-yyyy"));
      return string.Format("<a href='{0}' title='View all {3} posts for {2}'>{1}</a>",
          GetArchivePathForDay(e.Day), e.Day, date.ToString("d-MMM-yyyy"), count);
    }

    public string GenerateMonthLink(object sender, MonthLinkEventArgs e) {
      if (e.UseFullName) {
        return string.Format("<a href='{0}' title='View all posts for {1} {2}'>{1} {2}</a>",
                             GetArchivePathForMonth(e.Year, e.Month), GetFullMonthName(e.Month), e.Year);
      }

      string format = e.ForPrevMonthLink ? "&laquo; {0}" : "{0} &raquo;";
      string text = string.Format(format, GetAbbrMonthName(e.Month));
      return string.Format("<a href='{0}' title='View all posts for {1} {2}'>{3}</a>",
          GetArchivePathForMonth(e.Year, e.Month), GetFullMonthName(e.Month), e.Year, text);
    }

I'll admit I went a little overboard on the titles for the daily links, but it looks dead cool. At the moment I'm not too happy with the monthly links format (the "full name" option is for the caption, otherwise it generates a link for the calendar footer), but what the heck.

Finally, the Chalk class itself has two public methods you can use: both overrides of ShowCalendar(). There are also four other public method you can call in order to make the archive page display work (GetPostsForMonth(), etc).

    public string ShowCalendar() {
      return ShowCalendar("", "");
    }

    public string ShowCalendar(string yearAsString, string monthAsString) {
      int year, month;
      GetDateParts(yearAsString, monthAsString, out year, out month);

      GraffitiCalendar calendar = new GraffitiCalendar(year, month);
      return calendar.ToString();
    }

    public PostCollection GetPostsForMonth(string yearAsString, string monthAsString) {
      int year, month;
      GetDateParts(yearAsString, monthAsString, out year, out month);
      return PostsReader.GetPostsForMonth(year, month);
    }

    public PostCollection GetPostsForDay(string yearAsString, string monthAsString, string dayAsString) {
      int year, month, day;
      GetDateParts(yearAsString, monthAsString, dayAsString, out year, out month, out day);
      return PostsReader.GetPostsForDate(year, month, day);
    }

    public string GetMonthDisplayName(string yearAsString, string monthAsString) {
      int year, month;
      GetDateParts(yearAsString, monthAsString, out year, out month);
      return new DateTime(year, month, 1).ToString("MMMM yyyy");
    }

    public string GetDateDisplayName(string yearAsString, string monthAsString, string dayAsString) {
      int year, month, day;
      GetDateParts(yearAsString, monthAsString, dayAsString, out year, out month, out day);
      return new DateTime(year, month, day).ToLongDateString();
    }

    private int ConvertDatePart(string valueAsString, int defValue, int low, int high) {
      int value;
      if (string.IsNullOrEmpty(valueAsString) || !int.TryParse(valueAsString, out value))
        return defValue;
      if (low <= value && value <= high)
        return value;
      return defValue;
    }

    private void GetDateParts(string yearAsString, string monthAsString, out int year, out int month) {
      DateTime now = DateTime.Now;
      year = ConvertDatePart(yearAsString, now.Year, 1990, 2099);
      month = ConvertDatePart(monthAsString, now.Month, 1, 12);
    }

    private void GetDateParts(string yearAsString, string monthAsString, string dayAsString, out int year, out int month, out int day) {
      DateTime now = DateTime.Now;
      year = ConvertDatePart(yearAsString, now.Year, 1990, 2099);
      month = ConvertDatePart(monthAsString, now.Month, 1, 12);
      day = ConvertDatePart(dayAsString, now.Day, 1, DateTime.DaysInMonth(year, month));
    }

The rest of the code is about validating and converting the strings coming from the views or URLs (i.e., the big, bad, outside world). If someone is trying to spoof the URLs, or the values are invalid, the current month or current date is assumed. I thought this was a better plan than merely throwing an exception.

In the final segment, I'll talk about how to create the archive folder (actually the archive post) and the view that you must create for it.

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

Album cover for Hergest Ridge Now playing:
Oldfield, Mike - Part Two
(from Hergest Ridge)


Share it: Digg It!  StumbleUpon  Reddit  Del.icio.us  NewsVine  Furl  BlinkList  Ma.gnolia  Technorati

Writing an Archive Calendar, part 2

Next up in this series on writing the calendar of archived posts, is the PostsReader class. This class queries the database for the posts for a particular month or a particular day. It utilizes a couple of other helper classes that manage the caching of results, so we'll look at these first.

The first helper class is the PostCollectionSet. This is a set of already-computed collections of posts and is the granularity with which post collections are stored in the application cache. The set is declared as generic dictionary.

  public class PostCollectionSet : Dictionary<PostCollectionKey, PostCollection> {
    static string CacheKey = "jmbPostCollectionSet";

    public static PostCollectionSet GetInstance() {
      PostCollectionSet set = null;
      HttpContext context = HttpContext.Current;
      if (context == null)
        set = new PostCollectionSet();
      else {
        set = (PostCollectionSet)context.Cache[CacheKey];
        if (set == null) {
          set = new PostCollectionSet();
          context.Cache.Add(CacheKey, set, null, DateTime.Now.AddMinutes(10.0), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
        }
      }
      return set;
    }
  }

In essence, the GetInstance() method, queries the current HttpContext for the cache dictionary, and adds a new set if there isn't one present yet. The expiration is set to 10 minutes.

The PostCollectionKey class used as the key type in this dictionary is simple too: it's merely a class that stores the date of a post collection, with a special GetHashCode() method so it acts as a good key for this particular dictionary.

  public class PostCollectionKey {
    private int year;
    private int month;
    private int day;
    public PostCollectionKey(int year, int month, int day) {
      this.year = year;
      this.month = month;
      this.day = day;
    }
    public override int GetHashCode() {
      return (year * 1000) + (month * 100) + day;
    }
  }

Now we can look at the PostsReader class.

  public class PostsReader {

    private static Query BuildRangePostsQuery(DateTime start, DateTime end) {
      Query q = Post.CreateQuery();
      Column published = new Column("Published", DbType.DateTime, typeof(DateTime), "Published", false, false);
      Column isPublished = new Column("IsPublished", DbType.Boolean, typeof(bool), "IsPublished", false, false);
      Column isDeleted = new Column("IsDeleted", DbType.Boolean, typeof(bool), "IsDeleted", false, false);
      Column categoryID = new Column("CategoryID", DbType.Int32, typeof(Int32), "CategoryID", false, false);
      // this ordering is required by PostCount()
      q.OrderByDesc(published);
      q.AndWhere(published, start, Comparison.GreaterOrEquals);
      q.AndWhere(published, end, Comparison.LessThan);
      q.AndWhere(isPublished, true, Comparison.Equals);
      q.AndWhere(isDeleted, false, Comparison.Equals);
      q.AndWhere(categoryID, 1, Comparison.NotEquals); // uncategorized
      return q;
    }

    private static Query BuildMonthPostsQuery(int year, int month) {
      DateTime startOfMonth = new DateTime(year, month, 1);
      DateTime startofNextMonth = startOfMonth.AddMonths(1);
      return BuildRangePostsQuery(startOfMonth, startofNextMonth);
    }

    private static Query BuildDayPostsQuery(int year, int month, int day) {
      DateTime startOfDay = new DateTime(year, month, day);
      DateTime startofNextDay = startOfDay.AddDays(1);
      return BuildRangePostsQuery(startOfDay, startofNextDay);
    }

    public static PostCollection GetPostsForMonth(int year, int month) {
      return GetPostsForDate(year, month, 0);
    }

    public static PostCollection GetPostsForDate(int year, int month, int day) {
      PostCollectionSet set = PostCollectionSet.GetInstance();
      PostCollectionKey key = new PostCollectionKey(year, month, day);
      PostCollection posts = null;
      if (set.ContainsKey(key)) {
        posts = set[key];
      }
      else {
        if (day == 0)
          posts = PostCollection.FetchByQuery(BuildMonthPostsQuery(year, month));
        else
          posts = PostCollection.FetchByQuery(BuildDayPostsQuery(year, month, day));
        set[key] = posts;
      }
      return posts;
    }
  }

The interesting (and boring at the same time) method here is the BuildRangePostsQuery() method. It takes two dates (a start and end date), and constructs a query that can be used to fetch all of the published posts in that date range, ordered in descending order of published date/time, and not deleted or belonging to category 1 (the uncategorized category).

After that one, the next one is the workhorse GetPostsForDate() method. This works out if the requested post collection is in the cache, and if not, builds the query, executes it, and both stores the result in the cache and returns it to the caller.

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

Album cover for Greatest Hits Now playing:
Mott the Hoople - All the Way from Memphis
(from Greatest Hits)


Share it: Digg It!  StumbleUpon  Reddit  Del.icio.us  NewsVine  Furl  BlinkList  Ma.gnolia  Technorati

Writing an Archive Calendar, part 1

It's time to reveal all about that Archive Calendar I've been developing that now adorns my site. It's not quite done yet, but by the time I'll have it finished, this series of articles on how to write it will be too.

The first thing I did, before I got all embroiled in how to create a query and execute it to get a list of posts, and so on, was to make sure I knew how to create the calendar display. It's a basic HTML table, with a particular id, and the actual look and feel is provided by CSS.

We'll start off with a class I'm calling, for now, HtmlCalendar. It will have a Render() method that will return a string comprising the HTML markup for the month passed to the constructor. (Note I won't be showing absolutely all the code in this series, just the important stuff. I'll make the code available later on.)

  public class HtmlCalendar {

    public HtmlCalendar(int year, int month) {
      this.year = year;
      this.month = month;
    }

    public string Render() {
      StringBuilder sb = new StringBuilder();
      sb.AppendLine("<table id='jmbCalendar'>");
      AddCalendarCaption(sb);
      AddCalendarHeader(sb);
      AddCalendarFooter(sb);
      AddCalendarBody(sb);
      sb.AppendLine("</table>");
      return sb.ToString();
    }

  }

As you can see the Render() method merely creates a string builder, adds the opening tag for the table element, calls other methods to add the caption, the header row, the footer row, the body of the table, and finally closes off the table with the ending tag. The string builder's string is returned. The caption is the month being displayed as a calendar, the header is the days of the week (starting on Sunday) as initial letters, the footer is the two links to the previous and next month, and the body is the actual calendar.

Note something interesting: the footer is rendered before the body of the table, and yet will appear after it. This is actually a requirement for tables since the browser can more efficiently render the table before all the possibly many body rows are parsed. For print environments, for example, the footer may be rendered at the bottom of each page, so it would be very inefficient to read the entire body of rows before knowing what the footer looked like. The W3C validation suite will fail an HTML page on this point.

In order to render the links of the table, the calendar class would have to know about posts and links on the actual website. I didn't want this, so I created a couple of delegates that could be called whenever a link was required to insert into the HTML. If no delegate had been set, the calling code had some smarts to create some standard text.

  public delegate string DayLinkGenerator(object sender, DayLinkEventArgs e);
  public delegate string MonthLinkGenerator(object sender, MonthLinkEventArgs e);
    protected virtual string OnGenerateDayLink(int day) {
      DayLinkGenerator handler = GenerateDayLink;
      if (handler == null)
        return string.Empty;
      DayLinkEventArgs args = new DayLinkEventArgs(year, month, day);
      return handler(this, args);
    }

    protected virtual string OnGenerateMonthLink(int year, int month, bool fullName, bool forPrevMonth) {
      MonthLinkGenerator handler = GenerateMonthLink;
      if (handler == null) 
        return String.Empty;
      MonthLinkEventArgs args = new MonthLinkEventArgs(year, month, fullName, forPrevMonth);
      return handler(this, args);
    }

    private string GetMonthLink() {
      string monthLink = OnGenerateMonthLink(year, month, true, false);
      if (string.IsNullOrEmpty(monthLink))
        monthLink = string.Format("{0} {1}", GetMonthName(month), year);
      return monthLink;
    }

    private string GetAbbrMonthLink(int year, int month, bool forPrevMonth) {
      string monthLink = OnGenerateMonthLink(year, month, false, forPrevMonth);
      if (string.IsNullOrEmpty(monthLink))
        if (forPrevMonth)
          monthLink = string.Format(PrevMonthFormat, GetAbbrMonthName(month));
        else
          monthLink = string.Format(NextMonthFormat, GetAbbrMonthName(month));
      return monthLink;
    }

Once that plumbing was out of the way, the code to add the caption, header and footer was pretty simple. In essence, it was a case of remembering to start and end the various elements (row groups, rows, cells, etc) in the table.

    private void AddCalendarCaption(StringBuilder sb) {
      sb.Append("<caption>");
      sb.Append(GetMonthLink());
      sb.AppendLine("</caption>");
    }

    private void AddCalendarHeader(StringBuilder sb) {
      sb.Append("<thead>");
      sb.Append("<tr>");
      foreach (char d in DayLetters)
        sb.AppendFormat("<th>{0}</th>", d);
      sb.Append("</tr>");
      sb.AppendLine("</thead>");
    }

    private void AddCalendarFooter(StringBuilder sb) {
      int prevMonth = month - 1;
      int prevYear = year;
      if (prevMonth == 0) {
        prevMonth = 12;
        prevYear = year - 1;
      }

      int nextMonth = month + 1;
      int nextYear = year;
      if (nextMonth == 13) {
        nextMonth = 1;
        nextYear = year + 1;
      }
      DateTime firstOfNextMonth = new DateTime(nextYear, nextMonth, 1);
      bool displayNextLink = (firstOfNextMonth <= DateTime.Now);

      sb.Append("<tfoot>");
      sb.Append("<tr>");
      sb.AppendFormat("<td colspan='3'>{0}</td>", GetAbbrMonthLink(prevYear, prevMonth, true));
      sb.Append("<td colspan='1'>&nbsp;</td>");
      if (displayNextLink) 
        sb.AppendFormat("<td colspan='3'>{0}</td>", GetAbbrMonthLink(nextYear, nextMonth, false));
      else
        sb.Append("<td colspan='3'>&nbsp;</td>");
      sb.Append("</tr>");
      sb.AppendLine("</tfoot>");
    }

Note that the next month link is only rendered if it's not in the future.

The AddCalendarBody() method is the most fun. Look at a printed calendar. You'll see that every month in the calendar has at least four rows — it will only have exactly four rows for a February in a non-leap year where the 1st is on a Sunday. Conversely, there are some months that will have six rows, the last one being November 2008. So our table body, at most, will have 6 rows of 7 cells in each row.

The way I approached this is to create an array of 42 elements (6 × 7). The logical way to look at this array is six weeks, where each row represents a week, Sunday, Monday, through to Saturday. So if the first day of the month is on a Sunday, I need to fill in element [0] first, and then the next however many elements as there are days in the month. If the first day of the month is a Monday, I fill in element [1] first and then continue, if Tuesday, element [2} and then onwards, and so on. Generally the fifth row will be rendered, and rarely the sixth.

    private static int AddCalendarRow(StringBuilder sb, string[] days, int cdn) {
      sb.Append("<tr>");
      for (int i = 0; i < 7; i++) {
        sb.AppendFormat("<td>{0}</td>", days[cdn++]);
      }
      sb.AppendLine("</tr>");
      return cdn;
    }

    private void AddCalendarBody(StringBuilder sb) {
      sb.AppendLine("<tbody>");

      // a month calendar has a maximum of 6 rows, 42 cells
      string[] days = new string[42];

      int firstEntry = (int)new DateTime(year, month, 1).DayOfWeek;
      int lastDay = DateTime.DaysInMonth(year, month);
      int day = 1;
      for (int i = firstEntry; i < firstEntry + lastDay; i++) {
        string dayLink = OnGenerateDayLink(day);
        if (string.IsNullOrEmpty(dayLink))
          dayLink = DayNumbers[day];
        days[i] = dayLink;
        day++;
      }

      // there will always be at least 4 rows
      int cdn = 0;
      for (int i = 0; i < 4; i++) {
        cdn = AddCalendarRow(sb, days, cdn);
      }
      // rows 5 and 6 may or may not be there
      if (!string.IsNullOrEmpty(days[cdn])) {
        cdn = AddCalendarRow(sb, days, cdn);
        if (!string.IsNullOrEmpty(days[cdn])) {
          cdn = AddCalendarRow(sb, days, cdn);
        }
      }

      sb.AppendLine("</tbody>");
    }

So, as this code shows, the creation of the body of the table is a two step process: first, work out what goes in each cell, and then render the rows and cells using standard markup. As I mentioned some time ago, this is exactly how I did it back in 1991 for a Turbo Vision calendar control.

As it stands, the class can be tested on its own in a simple test harness. I wrote some test code that didn't set the two delegates (the one for the month links and the second for the day links) and just created a whole set of calendars to check. The calendars therefore didn't have any links, but at least they were visible and had valid data.

Here's an example of the HTML generated for January 2009:

<table id='jmbCalendar'>
<caption>January 2009</caption>
<thead><tr><th>S</th><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th></tr></thead>
<tfoot><tr><td colspan='3'>&laquo; Dec</td><td colspan='1'>&nbsp;</td><td colspan='3'>&nbsp;</td></tr></tfoot>
<tbody>
<tr><td></td><td></td><td></td><td></td><td>1</td><td>2</td><td>3</td></tr>
<tr><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td><td>10</td></tr>
<tr><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td></tr>
<tr><td>18</td><td>19</td><td>20</td><td>21</td><td>22</td><td>23</td><td>24</td></tr>
<tr><td>25</td><td>26</td><td>27</td><td>28</td><td>29</td><td>30</td><td>31</td></tr>
</tbody>
</table>

The CSS to style this is simple: just hang everything off the table's id. Here's what I have for my style sheet:

#jmbCalendar {border-spacing:0px;border-collapse:collapse;margin:0 auto;}
#jmbCalendar caption {margin:5px auto 5px auto;}
#jmbCalendar thead th {border:1px solid #5F5F5F;margin:0;}
#jmbCalendar tfoot td {border:1px solid #5F5F5F;margin-top:5px;text-align:center;}
#jmbCalendar tbody td {padding:2px 4px;text-align:right;}

Next time we'll look at the code that creates an instance of this class to see what it does.

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

Album cover for Seven Ways Now playing:
Van Dyk, Paul - People
(from Seven Ways)


Share it: Digg It!  StumbleUpon  Reddit  Del.icio.us  NewsVine  Furl  BlinkList  Ma.gnolia  Technorati

Archive Calendar now caches post collections

A minor fix, this. The Archive Calendar on the right over there now has support for caching monthly and daily post collections so that the potentially expensive query plus retrieval from the database doesn't happen every time.

Archives calendar snapshot In essence, since everyone who visits the home page for this blog will generate the current month's calendar, the collection of posts for January will remain in the cache, ensuring rapid access for everyone. Nevertheless, just in case, the cache has an expiration of 10 minutes, so the posts collection for the current month will be queried and generated once every 10 minutes.

This also means that if someone does click the link for the previous month, the collection of posts for that month will only be generated once, despite the page view requiring that collection twice. once for the list of posts and once for the calendar display. Again this is subject to the 10 minute cache expiration.

There is one more change to make, but it can't automatically be done with a Chalk extension. If I add a post (or, for that matter, delete one), the action should remove the collection of posts for the post's month (and date), if present, from the cache. This requires adding an event handler to the "a post was added" event, and this requires a plug-in. After all, Chalk is about creating the data on a page and only executes in that context, so I'm going to guess that the next move is to make the calendar a widget.

Otherwise, the tooltips for each link on a day in the calendar now show the number of posts for that day.

Now playing:
Stranglers - Strange Little Girl
(from The Collection 1977-1982)


Share it: Digg It!  StumbleUpon  Reddit  Del.icio.us  NewsVine  Furl  BlinkList  Ma.gnolia  Technorati

New Archive Calendar

This afternoon, for want of anything better to do with the freezing cold outside and the housework inside, I wrote an archive calendar chalk extension for the website. It's over there on the far sidebar.

At present, it's a chalk extension and not a widget, although a widget should probably make more sense, given that it's in one of the Graffiti sidebars. However, for this first iteration I wanted to make a monthly calendar with links for those days when I'd published posts.

User stories were these:

  • The calendar should show a single complete month, with days when I'd posted something displayed as links.
  • The caption should show name of the month being displayed in the calendar.
  • The calendar caption should show the number of posts for that month.
  • Clicking on the calendar caption should go to a page listing the posts for that month. The calendar should stay the same on that new page.
  • Clicking on a day (providing it has posts and is a link) should go to the page listing the posts for that exact date. The calendar on that new page should show the month for the date whose posts are being displayed.
  • Underneath the calendar there should be a link to the previous month. Clicking it should do two things, first display the page for that month, and second change the calendar to display that new month.
  • Also underneath the calendar there should be a link to the next month, providing that this month is not in our future (so, for example, on the home page, there is no link to the next month). Clicking it should display the page for that month, and also change the calendar to display the new month.
  • Tooltips for the  links should be specific to the month or date.

Up until now, I've been using an open-source Archive Widget, but haven't been that happy with it (for a start, it produces HTML that doesn't validate, you can only show posts for a single month, etc). Since it's written in VB, I wasn't particularly enamored with fixing it (I'm a C# guy at heart) and I really wanted a month calendar. But, nevertheless, I liked the way it worked, so I resolved to use the same infrastructure (that is, the same folder /archive, the same views (at least their names), and the same request structure for passing the date along to the page).

Still to do:

  • Make it a widget so that it can play in the admin setup for sidebar widgets.
  • Employ some kind of caching for the lists of posts in a given month. In essence the same list is generated to display the page as is to display the month in the calendar (the calendar needs the list in order to work out which days to make links and also for the post count in the caption). So for displaying the complete page for December 2008, for example, the same query is run twice on the database. The original archive widget had caching, so I ought to implement something similar.
  • Clean up the code. At the moment it's a huge violation of the SRP (Single Responsibility Principle), although no method exceeds CC 5.

I'll admit I'd started out trying to use DevExpress' ASPxCalendar control, but I ran into issues of how to host an external control inside a Graffiti view — let alone a sidebar widget. I shall have to play with that some more at a later stage (I fancy creating a page that shows the posts as a schedule using ASPxScheduler). So, for this implementation I wrote the calendar display code from scratch, although I did employ the same techniques as I used in 1991 for a Turbo Vision calendar control (scary, eh?). I used the look-n-feel that Eric Meyer has on his blog.

Now playing:
Eighth Wonder - J'ai Pas Peur
(from I'm Not Scared)

Share it: Digg It!  StumbleUpon  Reddit  Del.icio.us  NewsVine  Furl  BlinkList  Ma.gnolia  Technorati

About Me

I'm Julian M Bucknall, the M because it's my middle initial and because I and the other Julian Bucknall (the movie guy) would like to differentiate ourselves.

I'm a programmer by trade, an actor by ambition, and an algorithms guy by osmosis. I write articles for PCPlus in my spare time, not that there's much of that.

Julian M Bucknall Apart from that, an ex-pat Brit, atheist, microbrew enthusiast, Pet Shop Boys fanboy, slide rule and HP calculator collector, amateur photographer, Altoids muncher.

DevExpress

I'm Chief Technology Officer at Developer Express, a software company that writes some great controls and tools for .NET and Delphi. I'm responsible for the technology oversight and vision of the company.

The OUT Campaign

The OUT Campaign

Validation

Valid XHTML 1.0 Transitional     Valid CSS!

Bottom swirl

Archives

March 2010 (6)
SMTWTFS
« Feb  
123456
78910111213
14151617181920
21222324252627
28293031

Like this Archive Calendar widget? Download it here.

Search

Google ads

My Tweets

  • @richardcobbett I've been trying my hand at http://is.gd/a4WcI for a little while. Gets me out & about. http://is.gd/a4Wp4
  • @richardcobbett I thought you were doing the "Photo a Day" thing?
  • @scottisafool Inline, good. Image slideshow thingy, not so good. S'OK for photos, not for say screenshots.
Bottom swirl