Posts tagged with 'graffiti'

Bug with comments and GraffitiCMS

One bug with Graffiti that's been driving me nuts ever since I started using the app over a year ago is that it assumes that a commenter will naturally add the http:// to the beginning of their website name. If they don't, the code that displays the comment later will force the URL to be absolute (essentially by prepending the Graffiti application's base URL to the name). Of course that link is then nonsense and leads to a 404 if someone clicks on it later.

Since I use Scott Cate's 404 manager, I see the common 404s as they accumulate, and these comment website errors have been driving me nuts.

So, I've fixed all the comments that needed fixed on the site (I used SQL Server Management Studio rather than the Graffiti admin interface) and now I've fixed the code that saves a comment. If you have downloaded the source and upgraded your site the fix is pretty simple. Find comments.cs in the Data folder of Graffiti.Core and change the code that sets up the WebSite property to:

        else {
          if (!string.IsNullOrEmpty(WebSite)) {
            WebSite = HttpUtility.HtmlEncode(WebSite).Trim();
            if (!WebSite.StartsWith("http://", true, null)) {
              WebSite = String.Format("http://{0}", WebSite);
            }
          }

          if (!string.IsNullOrEmpty(Email))
            Email = HttpUtility.HtmlEncode(Email);

In other words instead of just HTML encoding it, encode it, trim, and make sure it starts with http://. Build, copy the new DLLs to the bin folder of your web site, and you're done.

(Note: OK, OK, the test should be a little more sophisticated since it will screw up URLs that use another scheme apart from http. Hasn't happened yet though.)

Album cover for Eccentrix RemixesNow playing:
Yello - On Track [Doug Laurent's First Journey]
(from Eccentrix Remixes)


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

Open source GraffitiCMS and IIS6

Color me stupid, but then again I was under some emotional pressure at the time. The open source version of GraffitiCMS has some changes in it compared to the final official commercial release. Well, duh, I suppose; and of course I'd mentioned some of them in my previous blog post on the subject.

There's another change, one that bit me good and proper. My old friend Scott Bussinger alerted me to it pretty much straight away, but I wasn't able to properly diagnose the issue without internet access (my parents, where I was staying, do not have broadband). It seemed that, although I could post new blog entries and they would show up on the front page of the blog, the actual page for the blog post was missing, causing a 404. I fixed it at the time manually, that is, by adding the required folder and default.aspx file in it. I left it until I got home to properly debug the situation; after all, I wasn't posting that much, being away.

It turns out that Telligent were working on a new feature in GraffitiCMS to take advantage of ASP.NET routing, something that's pretty much only available in IIS7. The hint came from this discussion on CodePlex when Scott Watermasysk stated that Graffiti's routing support is enabled by default, whether the Graffiti instance is running on IIS7 or not. (He also states that "Routing support [in GraffitiCMS] should be considered alpha quality code as [it has] not yet been tested outside of the developers who added [it].")

Since I run this blog on IIS6 on GoDaddy, the solution was to turn this routing support off. In the new Configuration page of the Site Options admin section, there's an option labeled "Generate Folders for Posts/Categories/Tags (Legacy)". This should be checked.

Configuration dialog

Once it is checked (and Graffiti restarted), publishing a blog post will create the folders and default.aspx files as before.

That same discussion I mentioned above seems to indicate that the routine support does have problems even in IIS7. I've got a test site on IIS7 that I'm playing around with so I'll be investigating in my spare time.

Album cover for CluesNow playing:
Palmer, Robert - Looking for Clues
(from Clues)


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

Converting to the open source GraffitiCMS

Once GraffitiCMS had been posted as open source, I downloaded it with the intent of upgrading this site to it. There wasn't much news on the Graffiti CodePlex page about what had changed since the latest official release (1.2). In particular, no news whether any of the proposed plans from the beginning of the year had been implemented, partially or not.

Since I wanted to fix certain problems with the product, I had to first make sure that my site worked with the released open source version. After that, I would be free to make the changes I needed. Here's a series of notes about my experiences in upgrading an existing site to the open-source Graffiti in case it helps someone else.

1. First things first. GraffitiCMS was originally released with VistaDB as the default database engine. Since VistaDB is a commercial product, Telligent could not provide it as part of the download. You should either purchase VistaDB itself if your site uses it as the database, or, more long-windedly, you should upgrade the database to SQL Server. As it happened I'd already done this a few weeks ago.

2. I downloaded the full existing site from GoDaddy's servers to create a test site on my PC, and added/configured it in IIS7. I also took a backup of the SQL Server database from GoDaddy, and restored it to a local instance on the same PC. I changed the web.config file so that the connection string pointed to the local database and tried the test site. It came up just fine. (Note: yeah, yeah, the victors rewrite history and all that. The first time I brought up the test site, I was wondering why it was taking so long so something running entirely locally. I'd of course forgotten to change the connection string, and so Graffiti was connecting to my public database over the internet. Sigh.)

3. Now that I had a baseline, it was time to "upgrade". I opened up the Graffiti solution in Visual Studio, rebuilt it, and copied the new DLLs over into the /bin folder of the test site. Running the site crashed Graffiti. Hmm.

4. The error was in global.ashx, so I decided to diff the old and the new versions to see what difference there was. In essence the interface to SiteSettings has changed: there is now an explicit method call to get the singleton object. So, I copied over the new global.ashx and all the aspx files as well (the diff showed some of them had changed too). I copied over the new web.config, changed the connection string, and brought the site up. It displayed the home page fine, and then I played around with some of the features to check.

5. Adding a new post though would crash the site. This was a puzzler: I checked the diffs of all the files and everything and they were the same. What was going on? Finally it came to me: on my test site I was using a several plug-ins (including a couple of mine); maybe one of those was at fault? The first identified was Scott Watermasysk's plug-in DLL (ScottWatermasysk.Graffiti.Plugins). This provides Twitter notification for new blog posts and provides a list of related posts for an RSS feed. Scott provides the source, so it was just the work of a moment to recompile, locate the usages of SiteSettings and modify them to use the new Get() syntax. Recompile, retest, and crash.

7. It turns out that another plug-in was responsible: the BlogExtensions plug-in, provided by Telligent. This plug-in provides several services, of which I use 2: the comments RSS feed and the pingback/trackback support. I didn't have the source already, but found it on the Google code site. Again the matter of moments to locate issues, fix, recompile.

8. There was one final issue to fix: there's a new item in the Site Options part of the Graffiti Admin pages called Configuration. All that was needed was to create the folder and copy the relevant ASPX file to the test site.

I now have an upgraded baseline using the open-source Graffiti, one from which I could fix bugs, test, and deploy. The first item was to add support for Posterous, but that's a tale for another day.

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

GraffitiCMS has been released as Open Source

It's finally happened. After almost a year of no progress on GraffitiCMS, the blogging engine I use for this site, it's now available as open source on CodePlex, using a Microsoft Reciprocal License (Ms-RL). Go get it!

Album cover for The EyeNow playing:
Yello - Distant Solution
(from The Eye)


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

Converting GraffitiCMS from VistaDB to SQL Server on GoDaddy

Is that SEO-specific enough, do you think? Heh. Anyway, here's the situation. I started this blog using GraffitiCMS about a year ago, previously having used static web pages created with CityDesk. All my sites are hosted on GoDaddy: I went with a Deluxe hosting option which means I can host pretty much as many sites as I want to in the same single folder tree.

At the time — can't remember why — I decided to stick with the default VistaDB as the database instead of opting to go to SQL Server. Throughout the year, things have been good, no issues whatsoever, so my decision seemed sane. Until about two weeks ago that is, when everything went to hell in a handcart in a hurry. Essentially, for a whole week, this blog went offline:

Visits fall off a cliff 

I still don't know what was up. I uploaded a blog post the day before, then, blam, November 15th was a black hole that lasted 6 days. As detailed here, I tried to fix it once I was back home from PDC — thought I'd had, but ever since it's been acting weird. Every now and then, for no discernable reason I can see, some process (obviously an ASP.NET IIS process) locks some section of the VistaDB database file and it's curtains for the blog until the process resets, several hours later. I'm guessing that it's some bug in GraffitiCMS and one request is hanging, causing everything else to hang. Or it's because something is timing out from taking too long. At any rate the log slowly starts to fill up with errors, since every three minutes or so some process kicks off to clean out deleted records:

Error logs

Plus the site has suddenly been acting as if it's very tired. It's just been plain slow. Just nasty to use (I was counting something like 7 seconds for a blog post to come up).

Finally yesterday I'd had enough. The site locked up again (for about 5 hours this time), so it was just time to move to SQL Server. At least with that I have some diagnostic tools and the like to find out what's going on.

With my GoDaddy hosting plan I have the ability to have up to 2 SQL Server databases. Not many, Benny, but then again I only need one. First problem is that, by default, GoDaddy does not expose your SQL Server database for remote access. They provide a nice web-based tool to manage your database, but it seemed a little limiting to me: I wanted to use SQL Server Management Studio to manage my database. Plus, the only way I was going to be able to get all my data into the database was to do it via a remote connection.

So I fired off an email to support asking for the right to access my database remotely. I got a reply back after three hours saying that I should delete my current databases (after backing them up of course). Also they had to move my whole domain onto another hosting server in order to allow remote access to the database. No problem (I only had a test database to try out the standard GoDaddy tools and this was soon dropped), so I quickly replied that my domain was ready to be moved. Four hours later, I got a reply saying, in effect, that "due to its complex nature, your issue has been relayed to our Advanced Technical Support Team". OK, I let support take their course and went to bed.

This morning, I had a slew of messages saying that the move had been successful and that I could now access SQL Server remotely. Amazingly the whole site was much more responsive than before: I reckon the previous hosting server was a bit overloaded (perhaps this was also part of my problem?). This evening, then, was the time for the Big Conversion.

1. I logged into my hosting account on GoDaddy and created a new database. On the database creation page was a radio button to state whether this database could be connected to remotely or not, something that had been missing before:

Remote access FTW 

I made sure that the button was set to Yes. 5 minutes or so later, the database had been created.

2. I fired up SQL Server Management Studio (SSMS) 2008 and connected to the SQL Server URL given by the database configuration page, providing the user name and password I'd set up for the database. It connected just fine.

3. However, when I tried to open the Databases node in the Object Explorer, I got SQL Server error 916. My credentials were not able to access another database I'd never heard of. I went back to the SSMS server login dialog, and added my database name. Same error. I googled for the answer and found this article by Clay Burt that described the same issue. It made reference to this Microsoft Connect post about how to fix the problem. Once I did this and refreshed, I saw a huge long list of the databases on the server, of which mine was one. (Of course, the only database I was able to open was my own.)

4. I opened my database in SSMS and then ran the Graffiti_SQL_Schema.sql script (it's in the top level \Data folder of the GraffitiCMS install). This created the database schema that Graffiti uses. Then it was time for the data.

5. I copied my whole GraffitiCMS site from GoDaddy onto my local disk. I normally do this for backups every couple of days anyway, but this time I wanted to make sure I had the latest, most-up-to-date Vista VDB3 database file. I was going to be using the DataMover utility to copy the data over to SQL Server. I set an "under repair" default.html file in the root folder so that visitors would know something was going on.

6. Now it was time for DataMover. This utility is found in the \Data\Utility\Migrations folder of the GraffitiCMS install. It requires two databases: the source and the destination. For the source, I browsed to the folder containing the VDB3 file I'd just downloaded, and for the target, I entered in the .NET connection string that pointed to the GoDaddy SQL Server instance that was hosting my database. (GoDaddy provide this in their database control panel. Open the database control panel, click on the pencil icon next to your database, then click on Configuration. Find the one that says .NET, it's just a case of selecting it all, copy-and-pasting it, and then overwriting the dummy password with the real one). I clicked Copy Data and then started to write this blog post about how I did it all.

7. Once all the data had been copied to the SQL Server database, it was time to fix the web.config file that I'd downloaded. First of all, I located the current connection string. It looked like this for me:

<add name="Graffiti" connectionString="Data Source=|DATADIRECTORY|\boyetblog.vdb3" />

I changed it to look like this (with certain values changed to protect the innocent, namely, me):

<add name="Graffiti" connectionString="Data Source=boyetblog.db.999999999.hostedresource.com;Initial Catalog=BoyetBlog;Persist Security Info=True;User ID=userid;Password=password;" />

Now I had to change the data provider. Further down web.config, there's a line that said:

<add key="DataBuddy::Provider" value="DataBuddy.VistaDBProvider, DataBuddy"/>

And I changed it to look like this:

<add key="DataBuddy::Provider" value="DataBuddy.SQL2K5DataProvider, DataBuddy"/>

I then uploaded the changed web.config file to the root of my blog site.

8. Time to test. I played around with as many features of the site that I could, especially the admin pages, including reporting. All seemed to work just fine. In fact, better than that, it is rip-roaringly fast. Having the database engine on a separate machine from the hosted web server is just slick like skates on ice.

 

Album cover for World MachineNow playing:
Level 42 - World Machine
(from World Machine)


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

GraffitiCMS to be released as open source

Scott Watermasysk of Telligent has just announced on Twitter that GraffitiCMS (the CMS engine behind this blog) is going to be released as open source on December 11:

image

A few initial thoughts:

First: thank goodness they've decided to do something with the product. I would have preferred, perhaps, that they added new features and functionality and brought out a new "closed" version, but this is a good second best. I wonder if they'll be using codeplex for publishing it.

Second: I wonder if what they do release as open source has some of the things they've been working on, or whether it will just be the latest current version, 1.2. Either way, I don't particularly care: I know what I want to add to GraffitiCMS, and this will be my opportunity. (I also know where some bugs are, so I'll be able to finally fix them instead of waiting around.)

Third: part of me hopes that they keep the GraffitiCMS.com site up. There's some good information in the forums for example (as well as the "documentation", such as it is). It'd be a shame to lose it.

Fourth: GraffitiCMS uses some third-party components. I would imagine that, in order to recompile the product, we shall have to purchase those controls. (An example is VistaDB, for example.)

Anyway, I'm anxious to see what will transpire. I do hope that this news doesn't turn out to be a dead end, like the promises of version 2.

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

Fixing site problems

Bang in the middle of my trip to Los Angeles for PDC, on 15th November, this GraffitiCMS site you are reading (http://blog.boyet.com/) went down. Hard. It seemed to happen just after my last post as well.

I added a "site under repair" default.html file to the root directory while I investigated (it would be picked up first if no filename is entered in a browser address bar). I had absolutely no idea what was wrong. I couldn't log into the site (indeed, the main page didn't even come up) to check out the error logs. The site is hosted on GoDaddy, but when logged into that I couldn't see anywhere that described what was up. I could FTP into the file system on GoDaddy's servers, so I did make a backup just in case. I even reset the whole application on GoDaddy, removing it from IIS, and re-adding it, hoping that would help. After downloading the whole site via FTP, I attached to the local version with the IIS running on may laptop and it seemed to come up just fine.

Another problem was that even when the main page was up (it was as slow as heck in doing so), clicking on an in-site link would crash the application, producing the GraffitiCMS error page. Again, no idea why.

Finally I decided to take a look this evening. I downloaded the site again, this time to my desktop. I attached the local site to IIS7 and investigated. Pretty soon I got a server error (a Yellow Screen of Death) which indicated that I had a duplicate key in graffiti_Post_Statistics. OK, then. Onto the next problem.

I'm using GraffitiCMS in its default mode, using a VistaDB database. The only database management tool for VistaDB databases is part of the VistaDB database engine itself. Using a helpful post from DamianM (Migrating Graffiti CMS from VistaDB to SQL Server), I downloaded a trial version of VistaDB 3 and installed it. I then ran the Data Builder tool that's part of the product to repair and pack the database. I still got a duplicate key error on running the site, so I went back into the tool and deleted the record causing the key violation. After that all was fine: the site came up and I could navigate through to in-site URLs. I then uploaded the fixed database to GoDaddy's servers and the public site worked perfectly again.

However, this does not bode well for the future. The VistaDB trial I installed runs out in 29 days (it's a 30 day trial). VistaDB is up to version 4 now, and DamianX intimates that the database file format has changed; in other words, you have to use the Data Builder 3 tool to fix a VistaDB 3 database. I'm going to email Jason Short, the owner of VistaDB, to verify whether this is true or not (I can't find any such information on their site). If so, once the trial runs out, I'm SOL if this problem happens again. (Of course, if the current version of VistaDB works with VDB3 files — without upgrading them to some VDB4 format — then this is no problem.)

Also, although Graffiti comes with a Data Mover application (and you can copy the data for your site from one VistaDB database to another blank one), it would not have solved this particular problem. I had to physically delete the erroneous record from the table. At the very least, Telligent should have provided a simple database repair app with GraffitiCMS(heck, VistaDB provide the source code for Data Builder so you can do exactly that). But then again, Telligent don't particularly care about GraffitiCMS any more — version 2 was due "in early summer", but we now have snow here in Colorado with no new version in sight.

In conclusion, the short-term moral of the tale is that it's time to migrate this site to SQL Server. Not dissing the VistaDB folks here, but a database needs an easily downloadable management tool.

And the long-term moral is to use some other blogging engine.

Now playing:
Kool & the Gang - Jungle Boogie
(from Gold (CD 1))


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

Printer CSS - call to action

Ned Batchelder is a tech blogger I like to read, although he tends to deal with languages and situations I don't. Nevertheless he comes up with some great insights that have applicability to what I do and some great topics that extend what I know.

In a recent post, he brought to his readers' attention that good websites should have a proper printer CSS stylesheet, so that their content not only gets rendered well on the screen, but also on paper. Paper being paper, there's no point in rendering lists of links like sidebars and navigation areas. People, when they print, just want the content.

Now, my old website had a print CSS file, but I'd neglected to provide one for this new blog. Not any more though, I've added one — so it's too late for you to see what it was like printing a page from this site.

It wasn't too difficult. Unlike Ned who started with a blank slate and put stuff in, I essentially started off with the screen CSS and started chopping stuff out. As Ned recommends, mark the content you don't want to print with a display:none style. That got rid of the sidebars and the navigation bar, and I deleted all of the rules for the elements in them. It's a little rude to have backgrounds when printing, so I just got rid of all the cd:background styles. I had to change the font colors, obviously (some of the screen ones are very close to white, since they're output on a dark background). I then got rid of the floats, pretty much, and formatted the margins and padding to give a nice printed look. I made all links the same color as the text that surrounds them — since they're not clickable, it doesn't make sense to draw attention to them.

I then added the print CSS link tag to Graffiti's main layout.view file:

$macros.Style("print.css","print")  

This command retrieves the CSS file for the current theme folder.

Take a quick peek by going to my main page and selecting Print Preview in your browser. Now go to your blog and do the same. Remember, a good print experience is just as important as the screen experience: it tells your readers you take pride in what you do and say.

Now playing:
Thin Lizzy - Dancing in the Moonlight (It's Caught Me in Its Spotlight)
(from Wild One: The Very Best of Thin Lizzy (1 of 2))


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

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

GraffitiClient API: calling GraffitiCMS from a program

I have a whole set of posts on my static website that I'd like to transfer over to my Graffiti CMS site, leaving behind a redirect link. Since I was using CityDesk from Fog Creek to blog before, there's no migration tool available. Hey, no problem, I'm a developer at heart so it's just a simple case of opening up a a couple of databases, writing a conversion routine, and Bob's your uncle.

Even better, Graffiti exposes a web service that I can call. I only need to open ONE database, and the conversion method calls a method on the web service to add these converted posts! Brilliant. Sometimes I astound myself with my perspicacity.

But, boy finding out information about the web service is ruddy difficult and I ran into some issues when I did so. This post is a quick explanation of what you should do in order to use the Graffiti web service.

First of all, when you unzipped the Graffiti product you would have created three folders: Data. Utility, and Web. The latter you will have copied to your web site. In Utility there's a dll called GraffitiClient.dll which is the entryway to the Graffiti web service. (Yeah, I'm being pedantic here about where it is because at one point I was looking in web/_utility on the web site for this mysterious dll. Grasshopper, do not follow my path.)

In Visual Studio, create a new Console application. Add a reference to GraffitiClient.dll. Type the basic code to list the categories (the code shown is for this blog). By the way, do not miss the api folder name off the end of the URL.

using System;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GraffitiClient.API;

namespace GraffitiApiTest {
  class Program {
    static void Main() {
      Console.WriteLine("List of categories");

      GraffitiService gs = new GraffitiService("username", "password", "http://blog.boyet.com/api/");

      try {
        PagedList<Category> plc = gs.Categories.Get(new NameValueCollection());

        foreach (Category c in plc)
          Console.WriteLine(c.Name);
      }

      catch (GraffitiServiceException gse) {
        Console.WriteLine(gse.Message);
        Console.WriteLine(gse.StatusCode);
      }

      Console.WriteLine("..done");
      Console.ReadLine();
    }
  }
}

(Obviously, those aren't valid user credentials for my website.) I decided to create a "special" user in Graffiti to handle access through the web service rather than use my main one. So I created a user called MigrationUser, a contributor, changed the hard-coded user and password in the code above, compiled and ran the application.

Boom.

Thread was being aborted
500

What the... ? So I dutifully looked up error 500 and essentially all I could find was "The server ran into a problem. Tough. Talk to your system admin." Couldn't find anything relevant about "Thread was being aborted", although there was some muttering about timeouts. I went to the Graffiti logs: nothing there at all. Hmm.

I pulled out Reflector and started looking through the code paths. This is made slightly more tricky since there are two of course: one on the client and one on the server. I was guessing that it was the server one that was the most probable, but the only error 500 I found was in a bit of code for deleting a category. Didn't sound right. I did notice some code that talked about permissions; hmm, I wonder.

So I made MigrationUser an admin.

And the code worked.

There you have it: the 5 minute intro to GraffitClient.dll. Oh and make sure the user whose credentials you are using is an admin.

Now to write a migration tool.

Album cover for Greatest Hits Now playing:
Ace of Base - All That She Wants
(from Greatest Hits)



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

Twitter notification broken with Graffiti 1.2?

Nope, but, boy, was it a coincidence! I use Scott Watermasysk's Graffiti Plug-in library to add Twitter notifications when I add new posts to this blog. Well, the weekend before last, I upgraded to Graffiti CMS 1.2 and blogged about it. The plug-in duly tweeted it. No problem.

The next post I made, on 23-Dec about Dell XPS support, wasn't. I eventually noticed, and wrote a fake manual tweet, resolving to work out what the issue was. Ditto for the next few blogs — it seems my resolutions don't have the force of real action — until this lunchtime, when I buckled down with some reheated Chick-fil-A nuggets to work out the problem.

Since the Twitter notification failure happened after I'd installed Graffiti 1.2, I couldn't really blame the upgrade. I looked at the Graffiti log and saw an error that looked like this:

Twitter Plugin

Your tweet could not be sent: The remote server returned an error: (417) Expectation Failed.

Ha! Moments later, using the famed tech support line that is Google, I came across this blog post that detailed the exact problem. Twitter in their infinite wisdom don't accept the "100-Continue" expectation http header any more. The default for .NET apps is to supply it, and therefore .NET Twitter notification code has been failing ever since 23 December.

Since Scott provides the source code to his plug-in, it was the matter of moments to add the single line of code to turn off the 100-Continue expectation...

   1: //...
   2:  
   3: // Create the web request  
   4: System.Net.ServicePointManager.Expect100Continue = false; // <-- new line
   5: HttpWebRequest request =
   6:     WebRequest.Create("http://twitter.com/statuses/update.xml") as HttpWebRequest;
   7:  
   8: //...

..and this post will be the test.

Album cover for G-Stoned Now playing:
Kruder & Dorfmeister - Definition
(from G-Stoned)


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

Avatars configured for comments

A minor but quick change: I've included the ability for comments to show an avatar. Essentially sign up at gravatar.com, upload an image that "represents" you, and it'll then appear here if you comment.

For the Graffiti freaks out there, here's the line of code in my comments.view file (this is a file loaded by my post.view file that displays all the comments, plus the add-a-comment form):

$macros.gravatar($comment.Email, $comment.Username, $comment.IPAddress, "%{size ='60'}")

Enjoy.

Album cover for When It Falls Now playing:
Zero 7 - Passing By (Album Version)
(from When It Falls)


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

Now running Graffiti CMS 1.2

In the gap in between two shows, after posting the last blog, I downloaded Graffiti CMS 1.2 and upgraded this site. Using the shared WiFi in the theatre it took a little while, but that was all upload and download times. The actual "upgrade" was practically instantaneous ("copy these new files over the existing ones").

So, now running the latest bits...

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

Quickie Chalk extension for the tags issue

I mentioned in my previous blog post that the workaround I had for displaying the tags for a post vertically, rather than as a horizontal, comma-separated list, was flawed.

There are in fact two issues at play. First of all here's a magnified view of the tag list in Firefox 3.0:

image

As you can see, the biggest problem is that the clever CSS hackery I used means that the commas are still displayed in a line after the title, like an ellipsis with tails. At normal size this isn't really visible, but it's there nevertheless.

The second, bigger, issue is that in Chrome and Safari (they use the same rendering engine), the vertical tag list looks like this:

image

Which is just nasty. Now, I could just fiddle with the CSS again, but it's time to write a bit of C# and create a Chalk extension.

The basic idea is simple: write a class with one or more methods, decorate the class with ChalkAttribute, compile, and deploy.

I'll start off with the shell. Since I intend writing more "one-shot" Chalk methods, I'll arrange them all in a class called JmbChalk.

   1: using System;
   2: using System.Web;
   3: using Graffiti.Core;
   4:  
   5: namespace JmbChalk {
   6:   [Chalk("JmbChalk")]
   7:   public class JmbChalk {
   8:  
   9:     public string TagList(string tagList, string preTag, string postTag, string linkClass) {
  10:  
  11:       return string.Empty;
  12:     }
  13:   }
  14: }

From this you can see a couple of things in the boilerplate. First you must add Graffiti.Core as a reference and into the using list. It is this assembly that contains the ChalkAttribute class. I'm also adding System.Web because I'll be accessing a class there that will help me set up the url for each tag.

The Chalk method is called TagList. It takes four parameters: the taglist itself, some value before each tag (for example, <li> although it doesn't have to be an HTML element tag), some value after (for me, </li>) and then the class name to apply to the <a> element.

The method is pretty easy: split the taglist into individual tags, and then construct the HTML snippet for each tag, by concatenating everything together:

   1: public string TagList(string tagList, string preTag, string postTag, string linkClass) {
   2:   string result = string.Empty;
   3:  
   4:   if (string.IsNullOrEmpty(tagList)) 
   5:     return result;
   6:  
   7:   var separators = new char[]{',', ';'};
   8:   string[] tags = tagList.Split(separators, StringSplitOptions.RemoveEmptyEntries);
   9:   if (tags.Length == 0)
  10:     return result;
  11:  
  12:   string absolutePath = VirtualPathUtility.ToAbsolute("~/tags/");
  13:   string format = string.IsNullOrEmpty(linkClass) ? 
  14:     "{3}<a href=\"{0}{1}/\">{2}</a>{4}" :
  15:     "{3}<a class=\"linkClass\" href=\"{0}{1}/\">{2}</a>{4}";     
  16:   foreach (string tag in tags) {
  17:     string cleanUrl = Util.CleanForUrl(tag);
  18:     result += string.Format(format, absolutePath, cleanUrl, tag, preTag, postTag);
  19:   }
  20:   return result;
  21: }

As you can see the tags folder for Graffiti is off the root and called tags (so we need to calculate the absolute path for it), and the Util.CleanForUrl method is a utility function found in Graffiti.Core.

A quick compile, and then I added the JmbChalk dll to the /bin folder for the website.

The change to postheader.view was pretty simple after that:

   1: <li class="tags">
   2:     Tags:
   3:     <ul>
   4:     $JmbChalk.TagList($post.TagList, "<li>", "</li>", "taglink")
   5:     </ul>
   6: </li>

Once that was uploaded, I could remove the hackery from the CSS file and the tags list now renders the same across all browsers I test against (Firefox, IE7, Chrome, and Safari).

image

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

So I get bored easily...

No sooner do I finish my quick series on customizing a Wordpress theme for Graffiti (I, II, III, IV) than I throw it all away for a — gasp — hand-written one. Yes, I got bored with the browns and went with the grays.

There were two reasons for this: first, I wanted to use the entire wide-screen browser window and avoid the dead space on either side, yet still have a non-cluttered look, and, second, I wanted to use the visual ideas present in Adobe Lightroom in doing so.

image Adobe Lightroom is an application that is geared to the photographer's workflow where the image is king. Everything on the screen is deferential to that idea. So the entire display is built of shades of (dark) grays so that the image you're working on stands out. And even with those grays, it has been designed so that only the important textual information is in light grays or white, the rest blends in with the background so that it doesn't distract from the job in hand: manipulating your photos.

So I wanted that kind of behavior. For the blog, it's the content that is king, the blog posts. And so it's those that should stand out in the display, not the widgets on the side, not even when the metadata about the posts. That stuff can be there, sure, but it shouldn't distract from the real meat of the blog.

Going along with all that, I wanted to make the theme image-free. No little gradient PNGs here, no fancy-schmancy rounded corner GIFs there. Images that are displayed as part of the theme (like my photo of the Castlerigg stone circle) had to blend into the background: they're there for decoration, but should not distract.

After that, it was a case of finding the right layout for the blog. I spent an hour or so, trawling the Wordpress themes, but there was nothing there that (a) were plain enough, or (b) that were wide-screen enough.

In my travels, I came across Eric Meyer's blog. and it had the kind of layout I was looking for where, sure, there is a sidebar, but the content column filled the remaining space.

That was my layout inspiration, and with my palette of grays from Lightroom, I spent the last three evenings building and tweaking the CSS file and view files.

The hardest part was the damn tag list for each post. $post.TagList is a comma-separated string of tag names. $macros.TagList takes that string and creates a comma-separated lists of URLs to the various tag queries. I wanted a vertical list of tag URLs created as a bunch of <li> elements, but it seemed that the only way I could do this was write my own plug-in. Since I've not done this before yet, I put that idea on the back shelf. I had another idea that I fleshed out in my mind: write a javascript function that parses the taglist and creates the required list of URLs, but that was discarded in favor of a bit of CSS jiggery-pokery that styles the <a> elements in the <li> to be float:left and clear:left. As a workaround, it works very nicely as you can see, with only one small bug left, which, because of my underlying vision for the theme, is all but hidden.

Last night, just before midnight I "flipped the switch" and it went live.

There are still a few bugs, but I'll be fixing those this evening.

Now playing:
Moby - First Cool Hive
(from Everything Is Wrong)


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

Customizing Graffiti CMS, part IV

(In which I continue taking apart a Wordpress theme to make it work with Graffiti. Part I. Part II. Part III.)

We're finally ready for the last piece of the jigsaw. I've created the layout.view file that determines the common look-n-feel for the site based on the Chronicles theme, and the index.view file that determines what a list of posts looks like. It's now time for the post.view file, the view that determines how to display a single blog post.

Apart from the comments, in effect this is much the same as a single post in a list of posts, so we can reuse the HTML/Chalk code we have from index.view. Of course, there's no list of posts, and we use $post.Body and not $post.Excerpt  but that's pretty obvious.

Onto the comments section, then. Here's the PHP code for that.

<?php comments_template(); ?>

Yeah, well, that's brilliant, that. There's no analog in Graffiti, so we're just going to have to roll our own. The best way to do this is to go to the Chronicles webpage, and click on the Test Run link. You'll get an example blog with the theme. Click on the comments link on one of the fake posts, and you'll get an example single post with a comments section. From this you can "view page as source" in your browser, and copy/paste the comment HTML into your post.view file.

From this, and viewing one of the supplied Graffiti themes (so you can get the loop code and the names of the comments properties) it's a piece of cake to roll up the loop again into a chunk o' Chalk. Here's what I got:

   1: #foreach($comment in $comments)
   2:     #beforeall
   3:         #if($comments.Count == 0)
   4:             <h3 id="comments">No responses to $post.title</h3>
   5:         #elseif($comments.Count == 1) 
   6:             <h3 id="comments">1 response to $post.title</h3>
   7:         #else 
   8:             <h3 id="comments">$comments.Count responses to $post.title</h3>
   9:        #end
  10:        <dl class="commentlist">
  11:    #nodata
  12:        #if($post.EnableNewComments)
  13:            <div class="entry">
  14:                <p>Feel free to add a comment...</p>
  15:            </div>
  16:        #else
  17:            <div class="entry">
  18:                <p>No new comments allowed.</p>
  19:            </div>
  20:        #end
  21:    #each
  22:        <dt id="comment-$comment.Id" #if($comment.CreatedBy == $post.CreatedBy) class="author" #end>
  23:            <span class="comment_num">
  24:                <a href="#comment-$comment.Id" title="Permalink to this comment">#$count</a>
  25:            </span>
  26:  
  27:            <strong>
  28:                <a href="$comment.WebSite">$comment.Name</a>
  29:            </strong>
  30:  
  31:            on $comment.Published.ToString("MMM dd yyyy") at $comment.Published.ToString("h:mm tt")
  32:        </dt>
  33:  
  34:        <dd id="comment-body-$comment.Id" class="entry#if($comment.CreatedBy == $post.CreatedBy) author#end">
  35:            $comment.Body 
  36:            #if($isUser)
  37:                <a href="javascript:void(0);" 
  38:                    onclick="Comments.deleteComment('$urls.AdminAjax', $comment.Id,'comment-$comment.Id','comment-body-$comment.Id'); return false">
  39:                    Delete Comment
  40:                </a>
  41:            #end
  42:        </dd>
  43:    #afterall
  44:        </dl>
  45: end
  46:  
  47: if($post.EnableNewComments)
  48:    <h3 id="respond">Leave a Comment</h3>
  49:    <form action="$url" method="post" id="comment_form">
  50:  
  51:        #if($isUser)
  52:            <p class="unstyled">
  53:                Logged in as $user.ProperName <a href="$urls.Logout">[Logout]</a>
  54:            </p>
  55:        #else
  56:            <p>
  57:                <input class="text_input" type="text" name="author" id="author" value="" tabindex="1" />
  58:                <label for="author"><small>Name (required)</small></label>
  59:            </p>
  60:            <p>
  61:                <input class="text_input" type="text" name="email" id="email" value="" tabindex="2" />
  62:                <label for="email"><small>Mail (will not be published) (required)</small></label>
  63:            </p>
  64:            <p>
  65:                <input class="text_input" type="text" name="url" id="url" value="" tabindex="3" />
  66:                <label for="url"><small>Website</small></label>
  67:            </p>
  68:        #end
  69:  
  70:        <p>
  71:            <textarea class="text_input text_area" name="comment" id="comment" rows="7" tabindex="4"></textarea>
  72:        </p>
  73:        <p>
  74:            $macros.CommentButton("%{id='submit', tabindex='5', value='Submit'}") 
  75:            <div style="display:none;" id="comment_status"></div>
  76:            <input type="hidden" name="comment_post_ID" value="$post.Id" />                
  77:        </p>
  78:    </form>
  79: end

This code also contains the comment form, should the post allow further comments.

Actually, I discovered after a week or so that this code had a bug that I'd copied directly from one of the supplied Graffiti themes. If you look carefully, it attempts to identify whether a comment was written by the author of the post or not, and displays the comment differently.

#if($comment.CreatedBy == $post.CreatedBy)

Although $post.CreatedBy is the correct property on the post, $comment.CreatedBy does not exist for the comment. It's obvious from reading the rest of the code that it should be $comment.Name, but it did cause me a couple of anxious minutes. With this change in place, my comments get highlighted in a contrasting background color.

And with that, I've come to the end of my small set of posts on how to change a Wordpress theme into Graffiti. I'll admit to having made more changes than just those I've described here (I've widened the basic theme display — it suits posts with code better), but once you have the basics, you can tweak to your heart's content.

 

Now playing:
Everything But the Girl - The Heart Remains a Child
(from Walking Wounded)


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

Losing Permissions with GoDaddy

I noticed this a couple of weeks ago, but couldn't pin it down to anything. All of a sudden, the Graffiti application used to run this blog suddenly stopped working, throwing up its generic error page. After a little bit of investigation why, I found out that the write permissions on the blog root folder had been cleared.

So I set them back, and promptly forgot about it.

Well, it happened again yesterday afternoon and the only thing that I had done with regard to my website configuration was to create a new subdomain of boyet.com: acting.boyet.com. This is a convenient place to hold my acting résumé (I had an audition yesterday afternoon, and I thought this would be an ideal short URL to write down on the audition form — no slashes or other weird characters).

It seems that adding the new subdomain to boyet.com did two things. First, it added the target folder for the subdomain to my IIS settings. Don't know why, but I removed it. And then, second, it cleared the write permissions to my blog subdomain and without write permissions, Graffiti can't run and throws up the error page.

And then I remembered what had happened last time this had happened: I'd just registered imetjulian.com and added it to my hosting account.

So, bear this in mind when you have a hosting account with GoDaddy: if you are configuring your domains in your hosting account (either adding new ones, or adding a subdomain, or possibly removing some), the domain configuration application may reset any write permissions you may have set on folders. If you have one of these folders as an application root — and, really, there's no other reason to have write permissions set — then the application probably won't run properly. After you've finished configuring your domains, check any write permissions you may have set.

Now playing:
Heaven 17 - The Height of the Fighting(he-La-Hu)
(from Endless)



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

Customizing Graffiti CMS, part III

(In which I continue taking apart a Wordpress theme to make it work with Graffiti. Part I. Part II. Part IV. )

Last time I'd set up the layout.view file and completed it by adding the @childcontent statement. Now I need to think about the other two main view files, index.view and post.view, the ones that will supply the child content.

The index.view file is possibly the easiest at this stage, since we started out with analyzing the equivalent PHP file from the Chronicles theme.

The index.view file is meant to display a list of posts. This list could come from a search, from a category list, from a tag list, and so on. No matter what the source, the view must display the post title, date, author, category, tags, and the first few words from each post. The way Graffiti works is that the list will have a maximum of 10 posts in it, and so we shall need to consider the paging facilities in Garffiti since after a while it's likely that there will be more than 10 posts to display.

So, all in all, there's a lot to consider.

The way I constructed the layout.view file, the content view files are responsible for deciding whether they want to display none, one, or both sidebars, and then displaying them. The sidebars are rendered by Chronicles as

<?php include (TEMPLATEPATH . '/sidebar1.php'); ?>

So, all we need to do is (roughly) the same for Graffiti:

$macros.LoadThemeView("sidebarleft.view")

After changing the PHP code in sidebar1.php to sidebarleft.view I had this:

   1: <div id="sidebar1">
   2:     <div id="sidebar1_top"><!-- Hack for IE --></div>
   3:     <ul>
   4:         $macros.LeftSideBar("%{beforeWidget='<li class=\"widget\">', afterWidget='</li>', beforeTitle='<h2>', afterTitle='</h2>'}")
   5:     </ul>
   6:     <div id="sidebar1_bottom"><!-- Hack for IE --></div>
   7: </div><!--end of sidebar1 -->

Similarly for the right sidebar:

   1: <div id="sidebar2">
   2:     <div id="sidebar2_top"><!-- Hack for IE --></div>
   3:     <ul>
   4:         $macros.RightSideBar("%{beforeWidget='<li class=\"widget\">', afterWidget='</li>', beforeTitle='<h2>', afterTitle='</h2>'}")
   5:     </ul>
   6:     <div id="sidebar2_bottom"><!-- Hack for IE --></div>
   7: </div><!--end of sidebar2 -->  

The nice thing about the $macros methods that render the sidebar is that you can specify the HTML tags that go around the widget "content" and around the widget titles. I'll talk at another stage how to write a sidebar widget.

Now we have some PHP code that iterates through the posts:

   1: <?php if (have_posts()) : ?>
   2: <?php while (have_posts()) : the_post(); ?>

Graffiti's Chalk language has a foreach loop construct which will nicely replace this. The loop has the following syntax:

   1: #foreach($post in $posts)
   2:   #each (this is optional since it’s the default section)
   3:        <!--text which appears for each item-->
   4:   #before
   5:        <!--text which appears before each item-->
   6:   #after
   7:        <!--text which appears after each item-->
   8:   #between
   9:        <!--text which appears between each two items-->
  10:   #odd
  11:        <!--text which appears for every other item, including the first-->
  12:   #even
  13:        <!--text which appears for every other item, starting with the second-->
  14:   #nodata
  15:        <!--Content rendered if $items evaluated to null or empty-->
  16:   #beforeall
  17:        <!--text which appears before the loop, only if there are items matching condition-->
  18:   #afterall
  19:        <!--text which appears after the loop, only of there are items matching condition-->
  20: #end

As you can see, this a very rich for loop, geared to producing rich visual displays of lists.

The $posts list id provided by Graffiti when the view is rendered. The $post variable is defined by us, and we can call it what we want. It'll be used within the loop to expose the various interesting properties of the post itself.

   1: $post.Body -- the body of the post
   2: $post.CreatedBy -- the user who created the post
   3: $post.Url -- the site's url to the post
   4: $post.Title - the title of the post
   5: $post.Published -- the date/time that the post was published
   6: $post.CommentCount -- the number of comments for the post

From these properties and from the Chalk foreach loop we can convert the PHP code and HTML formatting to a Graffiti view.

   1: #foreach($post in $posts)
   2:   #each
   3:     <div class="post" id="$post.id">
   4:  
   5:       <div class="date">
   6:         $post.Published.ToString("MMM dd yyyy")
   7:       </div><!--end of date -->
   8:  
   9:       <div class="post_title">
  10:         <h2><a href="$post.Url" rel="bookmark" title="$post.Title">$post.Title</a></h2>
  11:         <div class="posted">
  12:           Posted by $post.CreatedBy at $post.Published.ToString("h:mm tt")
  13:         </div><!--end of posted -->
  14:       </div><!--end of post_title -->
  15:  
  16:       <br clear="all" />
  17:  
  18:       <div class="tags">$macros.TagList($post.TagList)</div><!--end of tags -->
  19:  
  20:       $post.Excerpt("<p>", "</p>" ,"Read more...", 300)
  21:       
  22:       <div class="meta">
  23:         Filed under : $macros.CategoryLink($post.Category) | $macros.CommentUrl($post, "%{anchor='comments'}")
  24:       </div><!--end of meta -->
  25:     </div><!--end of post -->
  26:  
  27:   #nodata
  28:     <div class="post">
  29:       Sorry, there are no posts matching your request.
  30:     </div>
  31:   
  32:   #end

The code to add paging is simplicity itself, although to my taste a little too simple since we cannot change the text that will be shown when there is a previous or next page of posts (the text is hard-coded in Graffiti.Core.dll). The one and only parameter is the CSS class name for the div that contains the previous/next paging links.

$macros.Pager("navigation")

(UPDATE: Scott Watermasysk informs me in the comments below that there is an overload that does allow you to set the previous/next paging link text:

$macros.Pager(CssStyle, PreviousText, NextText)

I've altered the calls to paging in my view files.)

Next time, we'll look at the final piece of the theme conversion puzzle.

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

Customizing Graffiti CMS, part II

(In which I continue taking apart a Wordpress theme to make it work with Graffiti. Part I. Part III. Part IV.)

Unpacking the downloaded zip file for Chronicles gave me this set of files:

image

OK, so I recognize the css file, but the rest are PHP files, and nothing like "layout". Oh well, time to investigate. index.php looks like it could be the equivalent of index.view, so let's take a look:

   1: <?php get_header(); ?>
   2:  
   3: <div id="main">
   4: <?php include (TEMPLATEPATH . '/sidebar1.php'); ?>
   5:  
   6:     <div id="blog">
   7:      <div class="entry">
   8:  
   9: <?php if (have_posts()) : ?>
  10: <?php while (have_posts()) : the_post(); ?>
  11:  
  12: <div class="post" id="post-&lt;?php the_ID(); ?>">
  13:  
  14: <div class="date">
  15: <?php the_time('M d Y'); ?>
  16: </div><!--end of date -->
  17:  
  18: <div class="post_title">
  19: <h2><a href="&lt;?php the_permalink() ?>" rel="bookmark" title="Permanent Link to <?php the_title(); ?>"><?php the_title(); ?></a></h2>
  20: <div class="posted">Posted by <?php the_author_posts_link(); ?> </div><!--end of posted -->
  21: </div><!--end of post_title -->
  22:  
  23: <br clear="all" />
  24:  
  25: <div class="tags"><?php the_tags('Tags: ', ', ', '&lt;br />'); ?></div><!--end of tags -->
  26:  
  27: <?php the_content('Read more &raquo;'); ?>
  28:  
  29: <div class="meta">
  30: Filed under : <?php the_category(', ') ?> | <?php edit_post_link('Edit', '', ' | '); ?>  <?php comments_popup_link('No Comments &#187;', '1 Comment &#187;', '% Comments &#187;'); ?>
  31: </div><!--end of meta -->
  32:  
  33: </div><!--end of post -->
  34:  
  35:     <?php endwhile; ?>
  36:         <div class="navigation">
  37:             <div class="alignleft"><?php next_posts_link('&laquo; Older Entries') ?></div>
  38:             <div class="alignright"><?php previous_posts_link('Newer Entries &raquo;') ?></div>
  39:         </div>
  40:     <?php else : ?>
  41:     <?php endif; ?>
  42:  
  43: </div><!--end of entry -->
  44: </div><!--end of blog -->
  45:  
  46: <?php include (TEMPLATEPATH . '/sidebar2.php'); ?>
  47:  
  48: <br clear="all" />
  49: </div><!--end of main -->
  50:  
  51: <?php get_footer(); ?>

The thing I took away here is that there's a loop getting and displaying posts (the while loop) -- not that I fully understood it straight away --, there's a call to get the header at the top, and a call to get the footer at the bottom, and there are some calls to get the two sidebars. In other words, there's no equivalent to layout.view, the PHP file generates the whole page to display a list of posts.

All right, no problem. Let's build the layout.view. I'm going to guess the preamble to the layout will be found in header.php:

   1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   2: <html xmlns="http://www.w3.org/1999/xhtml">
   3: <head profile="http://gmpg.org/xfn/11">
   4: <meta http-equiv="Content-Type" content="&lt;?php bloginfo('html_type'); ?>; charset=<?php bloginfo('charset'); ?>" />
   5: <title><?php bloginfo('name'); ?> <?php if ( is_single() ) { ?> &raquo; Blog Archive <?php } ?> <?php wp_title(); ?></title>
   6: <meta name="generator" content="WordPress &lt;?php bloginfo('version'); ?>" /> <!-- leave this for stats -->
   7: <link rel="stylesheet" href="&lt;?php bloginfo('stylesheet_url'); ?>" type="text/css" media="screen" />
   8: <link rel="alternate" type="application/rss+xml" title="&lt;?php bloginfo('name'); ?> RSS Feed" href="<?php bloginfo('rss2_url'); ?>" />
   9: <link rel="pingback" href="&lt;?php bloginfo('pingback_url'); ?>" />
  10: <?php wp_head(); ?>
  11: </head>
  12: <body>
  13: <div id="wrapper">
  14:  
  15: <div id="searchbar">
  16: Search this blog...
  17: <div id="searchtext"><?php include (TEMPLATEPATH . '/searchform.php'); ?></div>
  18: </div><!--end of searchbar -->
  19:  
  20: <div id="header">
  21: <div id="header_left_top"><!-- Hack for IE --></div>
  22: <div id="header_left">
  23:  
  24: <div id="subscribe">
  25: <a href="&lt;?php bloginfo('rss2_url'); ?>"><img src="&lt;?php bloginfo('template_directory'); ?>/images/subscribe.gif" border="0" alt="Subscribe to RSS Feed!"/></a>
  26: </div><!--end of subscribe -->
  27:  
  28:     <div id="navigation">
  29:     <div id="menu">
  30:         <ul>
  31:         <li class="&lt;?php if (((is_home()) &amp;amp;&amp;amp; !(is_paged())) or (is_archive()) or (is_single()) or (is_paged()) or (is_search())) { ?>current_page_item<?php } else { ?>page_item<?php } ?>"><span><a href="&lt;?php echo get_settings('home'); ?>">Home<?php echo $langblog;?></a></span></li>
  32:                 <?php $params = wp_list_pages('sort_column=menu_order&depth=1&title_li='); ?>
  33:            </ul>
  34:     </div><!--end of menu -->
  35:     </div><!--end of navigation -->
  36:     
  37: </div><!--end of header_left -->
  38:  
  39: <div id="header_right"><br />
  40: <h1><a href="&lt;?php echo get_option('home'); ?>/"><?php bloginfo('name'); ?></a></h1>
  41: </div><!--end of header_right -->
  42:  
  43:  
  44: <br clear="all" />
  45: </div><!--end of header -->

This is more like it. Lots of headery type stuff. I copied this into a new file called layout.view, and looking at a real layout.view that came with Graffiti, converted the calls to the PHP interpreter to calls to Graffiti's Chalk markup.

   1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   2: <html xmlns="http://www.w3.org/1999/xhtml">
   3:     <head>
   4:         <title>$title</title>
   5:         $macros.Style("style.css","screen")
   6:         <!--[if lte IE 7]>
   7:         $macros.Style("ie7.css","screen")
   8:         <![endif]-->
   9:         <!--[if lte IE 6]>
  10:         $macros.Style("ie6","screen")
  11:         <![endif]-->        
  12:         
  13:         $macros.Head()
  14:     </head>
  15:  
  16:     <body>
  17:         <img src='$macros.ThemeFile("images/CastleriggBrownTone.jpg")' border="0" class="centered" style="margin-bottom:0;"/>
  18:     
  19:         <div id="wrapper">
  20:             <div id="header">
  21:  
  22:                 <div id="header_left_top"><!-- Hack for IE --></div>
  23:                 <div id="header_left">
  24:  
  25:                     <div id="searchbar"><br />
  26:                         Search this blog...
  27:                         <div id="searchtext">
  28:                             <form action="$urls.Search" method="get" >
  29:                                 <input type="text" alt="search this site" class="search-box" value="Search..." name="q" id="ls" 
  30:                                     onblur="if (this.value == '') {this.value = 'Search...';}" 
  31:                                     onfocus="if (this.value == 'Search...') {this.value = '';}" />
  32:                                 <input type="hidden" id="searchsubmit" value="Search" />
  33:                             </form>
  34:                         </div><!--end of searchtext -->
  35:                     </div><!--end of searchbar -->
  36:  
  37:                     <div id="navigation">
  38:                         <div id="menu">
  39:                             <ul>
  40:                                 $macros.NavBar()           
  41:                                 <li><a href="$urls.rss">Subscribe</a></li>
  42:                            </ul>
  43:                         </div><!--end of menu -->
  44:                     </div><!--end of navigation -->
  45:     
  46:                 </div><!--end of header_left -->
  47:  
  48:                 <div id="header_right"><br />
  49:                     <h1><a href="http://blog.boyet.com">blog.boyet.com</a></h1>
  50:                 </div><!--end of header_right -->
  51:  
  52:                 <br clear="all" />
  53:              </div><!--end of header -->

image(As you can see I indented the code an awful lot and moved a couple of things around: the subscribe link was too big, so I made it part of the menu at the top, and I added my own image to the top of the page.)

The interesting Graffiti bits are the calls to the $macros Chalk object. $macros is a "library of tools to build a view" and here I've used the Style, Head, ThemeFile, and NavBar methods. These make use of the configuration of Graffiti on my website to embed the right URLs and to render the navigation bar setup in the Graffiti control panel.

You'll also see calls to $title, which is replaced by the main title for the site from the Graffiti control panel, and $urls, which renders various special URLs for the site (here,the RSS feed).

Converting the footer was even simpler.

Control panel - Title fieldFinally I completed the layout.view file by adding a call to $childContent. It's this magic method that renders the content of the page, and will include index.view or post.view accordingly. We'll talk about them next time.

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

Customizing Graffiti CMS, part I

There were several reasons for choosing Graffiti as the CMS for my blog, but I suppose two were at the forefront: first, it was written in ASP.NET, and second it was very customizable, even to the point of writing plug-ins in C#. Since, in theory, I know this platform, this made it an attractive choice.

But, and what a big but, the documentation for all this is pretty bad. Ugly I'd say. All of the help is written with and displayed in Graffiti, and there doesn't seen to be any rhyme or reason to it. Also the basic documentation is at one end of the spectrum ("you know nothing") and the advanced at the other end ("we assume you know an awful lot, but we aren't really going to tell you what you do know"). It also doesn't help that the topics are in descending order of being posted.

Since I disliked the themes that came with Graffiti, the first port of my customization call was simple theming.

To create a theme you need at least four files. The first is the obvious one, a CSS (Cacading Style Sheet) file. The other three define the three basic views of a Graffiti site: layout.view, index.view, and post.view. Actually that isn't quite right: the layout.view file defines the basic layout of each page on your site, and the other two are refinements. index.view defines the layout for a list of posts, and post.view defines the layout for a single post. The views are in fact HTML documents with some special markup that causes Graffiti to inject other views into the HTML.

In other words, layout.view shows the common parts of every single web site rendered by Graffiti. It will generally include the <head> element, and the beginning/ending <body> tags. The basic plumbing, if you like.

I'm getting old these days, and I never was a great designer, so I'd rather use someone else's theme than write my own from scratch. I was never really fond of my "theme" for the old web site (it looked pretty good, but it feels very brittle underneath), so I went out looking for themes to use and/or modify.

Don't bother with Graffiti themes, there just aren't that many. (With one caveat: collect them so you can look at how the author used the special Graffiti markup in the views. I've learned a lot from looking at how the markup is used. Oh, another thing: the special markup is known as "Chalk".)

Chronicles theme You don't have to look very far to see that there are a bazillion Wordpress themes out there. You can get free ones; you can pay for commercial ones that are more polished. The authors of the free ones generally ask you to mention them if you use their theme on your site.

After some looking, I found one called Chronicles. It's a very nice looking three column theme in shades of brown, a color I hadn't used before. I downloaded it and unzipped it. Yikes, PHP everywhere.

(To be continued in part II, part III, and part IV.)

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 (15)
SMTWTFS
« Feb  
123456
78910111213
14151617181920
21222324252627
28293031

Like this Archive Calendar widget? Download it here.

Search

Google ads

My Tweets

Bottom swirl