Graffiti ships with quite a few pre-built widgets. These cover common tasks such as adding a message to site, pulling in links from Del.icio.us, and showing a recent list of posts.
However, we knew it would not be possible to ship every conceivable widget out of the box. So we did the next best thing, we create a really simple way you (the developer) could quickly and easily write your own widget. These widgets could leverage outside data, call a custom web service, re-use an RSS feed, or even utilize the Graffiti data APIs.
To build your own widget, you need to complete the following steps:
By default, Widgets render with a column wide UnOrdered List. This can be changed on a per theme basis using the Macro methods LeftSideBar and RightSideBar.
With-in each individual LI element, an individual widget is rendered. By default a widget's Title is render as an H3 element and the Widget "data" is renderd by overriding the RenderData method.
There are a couple methods you can use and override to customize a Widget:
Widgets are stored in Graffiti's ObjectStore using XML Serialization. If (and when) you need to store additional data, you can simply add fields and properties to your derived class and the data will be persisted to the database when any changes are made.
Widgets leverage the Graffiti dynamic form API by deriving from Graffiti.Core.EditableForm. You can also leverage this same API to collect additional data from users within your Widget.
Graffiti has a built in API you can use to easily read external feeds. To make this really easy to do, instead of deriving from Graffiti.Core.Widget you should derive from Graffiti.Core.WidgetFeed.
Let's take a look at a couple of Graffiti's own Widgets.
Example One: The first example, "Title & Body" allows users to quickly and easily add a block of content to their site. The title is a regular textbox and the body is managed with a WYWIWYG editor.
Before we look at the code, here is what's going on in a couple of easy steps:
Finally, here is a look at the code:
/// <summary> /// A widget which displays a title and the a block of text /// </summary> [WidgetInfo("e7abe913-0de7-45a5-9cc5-6c6c5809e808", "Title and Body", "Represents a title and body")] public class TitleAndBody : Widget { /// <summary> /// Stores the Content. Title is part of the Wiget. /// </summary> public string HTML; /// <summary> /// Override name to give a more meaningful /// value when displayed in a list. /// </summary> public override string Name { get { if (string.IsNullOrEmpty(Title)) return "An empty box"; else return Title + " (Title and Body widget)"; } } /// <summary> /// Override DataAsNameValueCollection in include /// the HTML content. /// </summary> /// <returns></returns> protected override NameValueCollection DataAsNameValueCollection() { NameValueCollection nvc = base.DataAsNameValueCollection(); nvc["HTML"] = HTML; return nvc; } /// <summary> /// Override SetValues so we can pull HTML out of Form object /// and store it locally to be saved to the database. /// </summary> public override StatusType SetValues(HttpContext context, NameValueCollection nvc) { base.SetValues(context, nvc); HTML = nvc["HTML"]; return StatusType.Success; } /// <summary> /// Add our Title and HTML textboxes. /// </summary> /// <returns></returns> protected override FormElementCollection AddFormElements() { FormElementCollection fec = new FormElementCollection(); fec.Add(new TextFormElement("Title", "Title", "The title of the section")); fec.Add(new WYWIWYGFormElement("HTML", "Your Content", null)); return fec; } /// <summary> /// No processing, just render the data. /// </summary> /// <returns></returns> public override string RenderData() { return HTML; } }
Example Two: The second example, is from Graffiti's RecentComment widget. This widget allows users to include the last N published comments in his or her sidebar.
The steps to do this are very similar to the Title & Body widget above with one major difference. In this Widget, we need to do a bit more work in RenderData to format our comments.
[WidgetInfo("4eaf767d-c787-4e9c-aafc-e37d0ef3f70c", "Recent Comments", "Controls the display of a list of recent comments")] [Serializable] public class RecentCommentsWidget : Widget { public override string Name { get { if (string.IsNullOrEmpty(Title)) return "Recent Comments"; else return Title; } } public override string Title { get { if (string.IsNullOrEmpty(base.Title)) base.Title = "Recent Comments"; return base.Title; } set { base.Title = value; } } private int _categoryId = -1; public int CategoryId { get { return _categoryId; } set { _categoryId = value; } } protected override FormElementCollection AddFormElements() { FormElementCollection fec = new FormElementCollection(); fec.Add(AddTitleElement()); ListFormElement lfe = new ListFormElement("numberOFcomments", "Number of Comments", "The number of most recent comments to list"); lfe.Add(new ListItemFormElement("3", "3")); lfe.Add(new ListItemFormElement("5","5",true)); lfe.Add(new ListItemFormElement("10","10")); fec.Add(lfe); return fec; } public override string RenderData() { StringBuilder sb = new StringBuilder("<ul>"); sb.Append(new Macros().ULRecentComments(NumberOfComments)); sb.Append("</ul>\n"); return sb.ToString(); } protected override NameValueCollection DataAsNameValueCollection() { NameValueCollection nvc = base.DataAsNameValueCollection(); nvc["numberOFcomments"] = NumberOfComments.ToString(); nvc["categoryid"] = CategoryId.ToString(); return nvc; } public override StatusType SetValues(System.Web.HttpContext context, NameValueCollection nvc) { base.SetValues(context, nvc); NumberOfComments = Int32.Parse(nvc["numberOFcomments"]); CategoryId = Int32.Parse(nvc["categoryid"]); return StatusType.Success; } public override string FormName { get { return "Recent Comment Configuration"; } } public int NumberOfComments = 5;
Example Three: Finally, in our last example, we look at the Graffiti Twitter widget. Twitter is a very popular micro-blogging application which users use to create 140 character status updates called Tweets. Twitter has a couple of API end points, but here we will leverage a very simple RSS feed which contains our recent tweets.
Before we look through the code, there is one very important thing to call out. We are deriving from the WidgeFeed instead of Widget. This gives us access to the Graffiti FeedManager which will not only request any RSS, Atom, or Opml feed for us, but it will also own the responsibility of caching and storing this data ever hour. This way our site and pages are always very responsive, even when the service for our data may not be.
[WidgetInfo("3ec475ab-cd5c-47f6-8e37-e7752a46cc5a", "Twitter", "Twitter messages")] public class TwitterWidget : WidgetFeed { private string _UserName; public string UserName { get { return _UserName; } set { _UserName = value; } } private int _itemsToDisplay = 3; public int ItemsToDisplay { get { return _itemsToDisplay; } set { _itemsToDisplay = value; } } public override string FeedUrl { get { return "http://twitter.com/statuses/user_timeline/" + UserName + ".rss"; } } public override string RenderData() { StringBuilder sb = new StringBuilder("<ul>"); if (!string.IsNullOrEmpty(UserName)) { try { RssChannel channel = this.Document(); if (channel != null && channel.Items != null) { int min = Math.Min(channel.Items.Count, ItemsToDisplay); for (int i = 0; i < min; i++) { string desc = channel.Items[i].Description; int index = desc.IndexOf(":"); sb.Append("<li>" + ( (index > -1) ? desc.Substring(index + 1).Trim() : desc) + "</li>"); } } } catch(Exception) { } sb.Append("</ul>\n"); } return sb.ToString(); } public override string Title { get { if (string.IsNullOrEmpty(base.Title)) base.Title = "My Tweets"; return base.Title; } set { base.Title = value; } } public override string Name { get { return "Twitter"; } } protected override FormElementCollection AddFormElements() { FormElementCollection fec = new FormElementCollection(); fec.Add(AddTitleElement()); fec.Add(new TextFormElement("username", "UserName", "(your twitter username")); ListFormElement lfe = new ListFormElement("itemsToDisplay", "Number of Tweets", "(how many tweets do you want to display?)"); lfe.Add(new ListItemFormElement("1","1")); lfe.Add(new ListItemFormElement("3", "3", true)); lfe.Add(new ListItemFormElement("5", "5")); fec.Add(lfe); return fec; } protected override NameValueCollection DataAsNameValueCollection() { NameValueCollection nvc = base.DataAsNameValueCollection(); nvc["username"] = UserName; nvc["itemsToDisplay"] = ItemsToDisplay.ToString(); return nvc; } public override StatusType SetValues(HttpContext context, NameValueCollection nvc) { StatusType statusType = base.SetValues(context, nvc); if(statusType == StatusType.Success) { ItemsToDisplay = Int32.Parse(nvc["itemsToDisplay"]); UserName = nvc["username"]; try { RegisterForSyndication(); } catch(Exception ex) { statusType = StatusType.Error; SetMessage(context,ex.Message); } } return statusType; } }