I put the term widget in the same category as out-of-the-box. Out-of-the-box is a very appropriate term for how and why we bought our favorite software package in 1995. There was, in fact, a box with a stack of 3.5 inch disks, there was a printed instruction booklet, there was a set of well documented and well bordered features. Maybe there was some extensibility—the hint of some macro-based or API strategy and the ability for a top-notch developer to crack into the box and extend it for your business’ purposes. Fast forward to now. There is no box (good for the company not packaging it and shipping it, good for the environment not digesting it), documentation is online (with the new challenge in finding a balance between release management and immediate editorial changes), and the installation involves saying OK and grabbing your next cup of coffee. But, more importantly, whereas in 1995 you threw the box away and started marking up your copy of the instruction manual (errata available by including proof of purchase serial number—oh man, already threw the box away—and mailing to PO Box…), now, in 2010, you have entered into a relationship. You don’t (and shouldn’t) expect your experience with this software to be boxed in. You expect your documentation to be task driven, continually updated with new and more focused code samples. You expect a community to be there when you enter your search phrase and wonder if anyone ever had that exact same challenge before. You expect—and this is a really important one—you expect that the owner of the business process should be able to model that process with the software directly. You expect the developer to run alongside the business process owner (no baton passing here), building features and functionality that are derived from the business process model conversation your software was able to facilitate.
Sitecore does not ship in a box.
One reason—the features wouldn’t fit inside. Really, if anyone has recently had a demo of Sitecore, you have seen that the conversation can go for hours on end and still not touch half of the feature set. Building a product on such a solid foundation has yielded an amazingly expansive structure above.
The more important reason—Sitecore doesn’t claim to know your business better than you do. With all of the amazing features available in the product, one thing always holds true and consistent—the feature is built with the dedication to your ability to change it and model it to your business process needs. And if you’ve seen the demo, you realize that you’re using the incredibly intuitive Sitecore UI’s to do the modeling. If you want a software company to tell you what your business process needs are, I think I still have a box somewhere in my attic.
Oh yeah, the widget. I couldn’t think of a better title for this control based on the disorganized rant of this blog. While the widget is perfect for calculating throughput in a freshman Operations 101 class, I’ve found them to be less than satisfying in developing a Web site feature (“What is it?, “It’s the new widget we got out of the box because we bought on Black Friday”).
Let’s use it today though, as we look at a couple of quick concepts—the use of Web Server controls, and playing around with Fast Query.
Recently I worked on a proof of concept where it was important to show the ease of adding “widgets” to a Sitecore page. The scenario is one that we’ve all thought through for our site—someone is writing a news article and they want to add some things to the right column next to the story itself. For this, let’s consider the ability to add:
- An image
- A set of images
- Just any old block of HTML
- A really smart list of items that is relevant to my content item that I'm working on
A disclaimer here. There are MANY ways of tackling this and this little story isn’t trying to be a best practices discussion about the best way to create the information architecture for ease-of-use and extensibility. Instead, in going through this exercise we simply want to:
- Go through the steps of “hooking up” a Web Server control
- Play around with a couple cool Fast Query expressions
To create the new Web Server control, let’s create a class. The idea behind this class is that the Sitecore.Context.Item is going to have a Multilist field where a content author can select any of these prebuilt widgets to include on their page. The widgets themselves will be of four different varieties that you can see in the switch statement.
using System;
using System.Web.UI;
using Sitecore.Data.Items;
using Sitecore.Links;
using Sitecore.Web.UI.WebControls;
using System.Text;
namespace examples
{
public class widget: Sitecore.Web.UI.WebControl
{
protected override void DoRender(HtmlTextWriter output)
{
Item item = Sitecore.Context.Item;
if (item != null)
{
Sitecore.Data.Fields.MultilistField widgetsField = (Sitecore.Data.Fields.MultilistField)item.Fields["Widgets"];
if (widgetsField != null && widgetsField.Count > 0)
{
foreach (Item widget in widgetsField.GetItems())
{
switch (widget.TemplateName)
{
case "HTML Widget":
//todo: RenderHTMLWidget(output, widget);
break;
case "Single Image Widget":
//todo: RenderSingleImageWidget(output, widget);
break;
case "Query Widget":
//todo: RenderQueryWidget(output, widget);
break;
case "Image Gallery Widget":
//todo: RenderImageGalleryWidget(output, widget);
break;
default:
break;
}
}
}
}
}
}
}
Now we can compile our project…our new class isn’t ready to do anything just yet (we can investigate that later), but we can go through the steps to register the control with Sitecore. To do this, we can use the Developer Center user interface. We can also do this by simply creating a new content item and filling in the appropriate fields, but let’s use the Developer Center at least this first time.
Please see our full documentation set (especially the Presentation Component Cookbook and Presentation Component Reference) for much better documentation around this process. Below are steps to get us going quickly….
In Developer Center, click on File—>New. From there you will see the ability to create a new Web Control. Select Web Control and click Create.
Follow the Wizard until you get to the Configuration step:
Notes on these steps:
- Name will be the name of the content item
- Tag Prefix/Tag will be how the control is registered on the page
- Be sure to check your namespace and the check in your project for your assembly name (and you don’t need to add the .dll extension).
- Click the Test button to be sure your class was found in the assembly, and complete the Wizard by choosing where to create the Sitecore content item that represents your new control.
- After finishing the Wizard, the Developer Center will open to the item itself, so you can see how the item’s fields are populated with the results of your Wizard entry.
- To make your next Web Control, you could simply duplicate or copy/paste this item and change the fields to reflect your next class……or you can go through the Wizard steps again.
Here’s my final:
And here’s how the control could be registered and included in an .aspx:
<%@RegisterTagPrefix="mc"Namespace="examples"Assembly="myproject"%>
<%@PageLanguage="c#"CodePage="65001"AutoEventWireup="true"%>
<%@OutputCacheLocation="None"VaryByParam="none"%>
<!DOCTYPEhtml PUBLIC"-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<htmllang="en"xml:lang="en"xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Insert the page title here.</title>
</head>
<body>
<formmethod="post"runat="server"id="mainform">
<mc:widgetrunat="server"id="widget1"xmlns:mc="http://www.sitecore.net/xhtml">
</mc:widget>
</form>
</body>
</html>
Now we can get back to our class. I’ve included the shell that we saw before, and added a method to return the results of a Sitecore Fast Query:
using System;
using System.Web.UI;
using Sitecore.Data.Items;
using Sitecore.Links;
using Sitecore.Web.UI.WebControls;
using System.Text;
namespace examples
{
public class widget: Sitecore.Web.UI.WebControl
{
protected override void DoRender(HtmlTextWriter output)
{
Item item = Sitecore.Context.Item;
if (item != null)
{
Sitecore.Data.Fields.MultilistField widgetsField = (Sitecore.Data.Fields.MultilistField)item.Fields["Widgets"];
if (widgetsField != null && widgetsField.Count > 0)
{
foreach (Item widget in widgetsField.GetItems())
{
switch (widget.TemplateName)
{
case "HTML Widget":
//todo: RenderHTMLWidget(output, widget);
break;
case "Single Image Widget":
//todo: RenderSingleImageWidget(output, widget);
break;
case "Query Widget":
RenderQueryWidget(output, widget);
break;
case "Image Gallery Widget":
//todo: RenderImageGalleryWidget(output, widget);
break;
default:
break;
}
}
}
}
}
protected void RenderQueryWidget(HtmlTextWriter output, Item widget)
{
output.AddAttribute(HtmlTextWriterAttribute.Class, "Widget");
output.RenderBeginTag(HtmlTextWriterTag.Div);
output.AddAttribute(HtmlTextWriterAttribute.Class, "WidgetPanelTab");
output.RenderBeginTag(HtmlTextWriterTag.Div);
output.Write(FieldRenderer.Render(widget, "Title"));
output.RenderEndTag();//WidgetPanelTab Div
output.AddAttribute(HtmlTextWriterAttribute.Class, "WidgetPanelContent");
output.RenderBeginTag(HtmlTextWriterTag.Div);
string fastQuery = widget.Fields["Widget"].Value;
Sitecore.Data.Database db = Sitecore.Context.Database;
Item[] results = db.SelectItems(fastQuery);
foreach (Item result in results)
{
string menuTitle = result.Name;
if (result.Fields["Menu Title"] != null)
{
if (!string.IsNullOrEmpty(result.Fields["Menu Title"].Value.ToString()))
{
menuTitle = result.Fields["Menu Title"].Value.ToString();
}
}
if (result.Paths.IsMediaItem)
{
output.Write(string.Format(@"<li><a href=""{0}"">{1}</a></li>", Sitecore.Resources.Media.MediaManager.GetMediaUrl(result), menuTitle));
}
else
{
output.Write(string.Format(@"<li><a href=""{0}"">{1}</a></li>", Sitecore.Links.LinkManager.GetItemUrl(result), menuTitle));
}
}
output.RenderEndTag();//WidgetPanelContentTab Div
output.RenderEndTag();//Widget Div
}
}
}
Notice that the class inherits from the Sitecore.Web.UI.WebControl and must override the DoRender method (using and HTMLTextWriter object to add HTML to the output). For our RenderQueryWidget method, here are the assumptions:
- The Context Item (which caused this control to be invoked because it was part of the item’s presentation details) has a MuliList field called “Widgets”.
- In that MultiList field are 1 to N of these Widget items.
- The Widget items can be of 4 different data templates, representing the different types of widgets available
- If the Widget item is created from the Query Widget data template, the Widget item will have a single line text box with a Fast Query expression.
- The RenderQueryWidget will use that expression to grab the appropriate items from the content tree and render an unordered list of links to those items.
- The output will be a series of divs with the ability to call out a CSS class to format them.
Here’s a screenshot of how the Query Widget data template could be created. Some features to implement after this article. Having a single-line text field for the fast query is obviously not the most intuitive business champion interface, so we can start thinking of the criteria (and the Sitecore field type to go with it) that would provide a better user interface experience.
For now, let’s concentrate on some of the Fast Queries we could use. And for a detailed description of this feature, please see our Sitecore Query and Fast Query documentation. Also, if you want to test these expressions in your own installation, use the Developer Center Tools:XPath Builder feature.
| Need | Fast Query to solve |
| “Give me all the items that are based on the News Story Data Template (that are descendants of the content node)” | fast:/sitecore/content//*[@@templatename='news story'] |
| “Give me all items that have been tagged with the Professional OMS Profile” | fast://*[@__Tracking = '%Professional%'] |
| “Give me items that are definitely products in the home site that have also been tagged with the Male OMS Profile” | fast:/sitecore/content/home//*[@@templatename='product' and @__Tracking='%Male%'] |
Here’s how the Widget content item for the previous example could look:
Now, if you include a MultiList field (think of creating a separate Widgets Data Template and include it as an inherited template where needed). This template could just contain the MultiLIst field itself (sourced to find widgets in the content tree) where an author could select the appropriate widget for their content item.
I end with the full class listing below so you could start thinking of improved and extended uses of this. In this example, the Widget field itself is of a different type depending on the type of Widget (it’s an image field, it’s a Rich Text field, etc.). Tongue firmly implanted in cheek, a widget, of course, can do anything!
using System;
using System.Web.UI;
using Sitecore.Data.Items;
using Sitecore.Links;
using Sitecore.Web.UI.WebControls;
using System.Text;
namespace examples
{
public class widget : Sitecore.Web.UI.WebControl
{
protected override void DoRender(HtmlTextWriter output)
{
Item item = Sitecore.Context.Item;
if (item != null)
{
Sitecore.Data.Fields.MultilistField widgetsField = (Sitecore.Data.Fields.MultilistField)item.Fields["Widgets"];
if (widgetsField != null && widgetsField.Count > 0)
{
foreach (Item widget in widgetsField.GetItems())
{
switch (widget.TemplateName)
{
case "HTML Widget":
RenderHTMLWidget(output, widget);
break;
case "Single Image Widget":
RenderSingleImageWidget(output, widget);
break;
case "Query Widget":
RenderQueryWidget(output, widget);
break;
case "Image Gallery Widget":
RenderImageGalleryWidget(output, widget);
break;
default:
break;
}
}
}
}
}
protected void RenderQueryWidget(HtmlTextWriter output, Item widget)
{
output.AddAttribute(HtmlTextWriterAttribute.Class, "Widget");
output.RenderBeginTag(HtmlTextWriterTag.Div);
output.AddAttribute(HtmlTextWriterAttribute.Class, "WidgetPanelTab");
output.RenderBeginTag(HtmlTextWriterTag.Div);
output.Write(FieldRenderer.Render(widget, "Title"));
output.RenderEndTag();//WidgetPanelTab Div
output.AddAttribute(HtmlTextWriterAttribute.Class, "WidgetPanelContent");
output.RenderBeginTag(HtmlTextWriterTag.Div);
string fastQuery = widget.Fields["Widget"].Value;
Sitecore.Data.Database db = Sitecore.Context.Database;
Item[] results = db.SelectItems(fastQuery);
foreach (Item result in results)
{
string menuTitle = result.Name;
if (result.Fields["Menu Title"] != null)
{
if (!string.IsNullOrEmpty(result.Fields["Menu Title"].Value.ToString()))
{
menuTitle = result.Fields["Menu Title"].Value.ToString();
}
}
if (result.Paths.IsMediaItem)
{
output.Write(string.Format(@"<li><a href=""{0}"">{1}</a></li>", Sitecore.Resources.Media.MediaManager.GetMediaUrl(result), menuTitle));
}
else
{
output.Write(string.Format(@"<li><a href=""{0}"">{1}</a></li>", Sitecore.Links.LinkManager.GetItemUrl(result), menuTitle));
}
}
output.RenderEndTag();//WidgetPanelContentTab Div
output.RenderEndTag();//Widget Div
}
protected void RenderImageGalleryWidget(HtmlTextWriter output, Item widget)
{
output.AddAttribute(HtmlTextWriterAttribute.Class, "Widget");
output.RenderBeginTag(HtmlTextWriterTag.Div);
output.AddAttribute(HtmlTextWriterAttribute.Class, "WidgetPanelTab");
output.RenderBeginTag(HtmlTextWriterTag.Div);
output.Write(FieldRenderer.Render(widget, "Title"));
output.RenderEndTag();//WidgetPanelTab Div
output.AddAttribute(HtmlTextWriterAttribute.Class, "WidgetPanelContent");
output.RenderBeginTag(HtmlTextWriterTag.Div);
Sitecore.Data.Fields.MultilistField imageList = (Sitecore.Data.Fields.MultilistField)widget.Fields["Widget"];
foreach (Sitecore.Data.Items.MediaItem media in imageList.GetItems())
{
output.Write(string.Format(@"<a href=""{0}""><img src=""{0}"" width=""90px;"" alt=""{1}"" /></a>", Sitecore.Resources.Media.MediaManager.GetMediaUrl(media), media.Name));
}
output.RenderEndTag();//WidgetPanelContentTab Div
output.RenderEndTag();//Widget Div
}
protected void RenderSingleImageWidget(HtmlTextWriter output, Item widget)
{
output.AddAttribute(HtmlTextWriterAttribute.Class, "Widget");
output.RenderBeginTag(HtmlTextWriterTag.Div);
output.AddAttribute(HtmlTextWriterAttribute.Class, "WidgetPanelTab");
output.RenderBeginTag(HtmlTextWriterTag.Div);
output.Write(FieldRenderer.Render(widget, "Title"));
output.RenderEndTag();//WidgetPanelTab Div
output.AddAttribute(HtmlTextWriterAttribute.Class, "WidgetPanelContent");
output.RenderBeginTag(HtmlTextWriterTag.Div);
output.Write(FieldRenderer.Render(widget, "Widget", "mw=300"));
output.RenderEndTag();//WidgetPanelContentTab Div
output.RenderEndTag();//Widget Div
}
protected void RenderHTMLWidget(HtmlTextWriter output, Item widget)
{
output.AddAttribute(HtmlTextWriterAttribute.Class, "Widget");
output.RenderBeginTag(HtmlTextWriterTag.Div);
output.AddAttribute(HtmlTextWriterAttribute.Class, "WidgetPanelTab");
output.RenderBeginTag(HtmlTextWriterTag.Div);
output.Write(FieldRenderer.Render(widget, "Title"));
output.RenderEndTag();//WidgetPanelTab Div
output.AddAttribute(HtmlTextWriterAttribute.Class, "WidgetPanelContent");
output.RenderBeginTag(HtmlTextWriterTag.Div);
output.Write(FieldRenderer.Render(widget, "Widget"));
output.RenderEndTag();//WidgetPanelContentTab Div
output.RenderEndTag();//Widget Div
}
}
}
