Problem: let's imagine you have a website with a multiple pages where editors supposed to create content, consistent from pre-styled components, coming in various order and in various places. How would you do that?
Idea: to my mind, the most obvious answer would be to classify content to several atomic types. These can be Header, SubHeader, Paragraph, Link, LinkList, Image, BlockQuote etc. whatever you may need. These "primitives" on higher level are wrapped by container types, that server as data sources for certain renderings.
Implementation: imagine a page that has a main content with a sidebar. We want content authors to be able either to select from already pre-defined sidebar components (like LoginForm or let's say TwitterFeed component), or create a custom sidebar with some generic content, or both, and in various orders. In order to do that we need to associate SidebarHolder rendering with a corresponding placeholder, so that only that rendering would be possible to add to particular placeholder. After that done you need to associate related content (remember, that you may restrict location of those container data items from Datasource Location field of corresponding rendering item; also you may limit to specific container template with Datasource Template field).
We insert GenericContentSidebar container for our example. As soon as container is inserted as a datasource for the SidebarHolder rendering, you may start creating primitive content items as the children of a container. But before doing that you may consider assigning insert options for rendering's datasource container template item. To do that - go to Standard values of that template item and assign insert options for those primitives' templates you may want to have available for that particular datasource type.
Code: let's assume we are using controller rendering that addresses the following controller action:
public ViewResult GenericControlsHolder()
{
var content = BaseGenericControl.Iterate(RenderingContext.Current.Rendering.Item);
return View("/Views/Renderings/PageComponents/GenericControlsHolder.cshtml", content);
}
That controller rendering is passing List<dynamic> as model into GenericControllerHolder view. Let's see the implementation for BaseGenericControl class and Iterate() method within it. I also included two public properties into BaseGenericControl in order to be able to specify custom CSS class for wrapping tags in cases when they wrap output values in overridden Render().
I use dynamics instead of base class because not all of primitives derive from base class, but they all have render method, so if you want to be strict with your types - you may use interfaces instead (let's stay very simple here)
public class BaseGenericControl
{
public static class Constants
{
public static class Templates
{
public const string Title = "{38B8D602-9EB4-4DDA-973F-1CA48CC42E19}";
public const string SubTitle = "{FC85D20A-4DE7-476B-AC8D-0717BA8FD23A}";
public const string RichText = "{7EACE00C-552E-419F-9ACB-51A98998CA39}";
public const string Paragraph = "{8A4F87F2-E36D-4377-B5A0-9866E64D3AE5}";
public const string LinksList = "{7466407B-3839-4EBE-9FC7-20AF9217FFAB}";
public const string Link = "{D38B51D6-74CF-40E2-A86F-97D346C17128}";
public const string Separator = "{9542C6DA-2FDA-482C-A6CF-B07F85D3CEE5}";
}
}
public HtmlString Value { get; set; }
public HtmlString CssClass { get; set; }
public BaseGenericControl()
{
}
public BaseGenericControl(Item item)
{
Value = new HtmlString(FieldRenderer.Render(item, "Value"));
CssClass = new HtmlString(FieldRenderer.Render(item, "CSS Class"));
}
public virtual string Render()
{
return Value.ToString();
}
public static dynamic Iterate(Item datasourceItem)
{
var content = new List<dynamic>();
if (datasourceItem != null)
{
foreach (Item item in datasourceItem.Children)
{
if (item.TemplateID == new ID(Constants.Templates.Title))
{
content.Add(new Title(item));
}
else if (item.TemplateID == new ID(Constants.Templates.SubTitle))
{
content.Add(new SubTitle(item));
}
else if (item.TemplateID == new ID(Constants.Templates.Paragraph))
{
content.Add(new Paragraph(item));
}
else if (item.TemplateID == new ID(Constants.Templates.RichText))
{
content.Add(new RichText(item));
}
else if (item.TemplateID == new ID(Constants.Templates.LinksList))
{
content.Add(new LinksList(item));
}
else if (item.TemplateID == new ID(Constants.Templates.Link))
{
content.Add(new GenericLink(item));
}
else if (item.TemplateID == new ID(Constants.Templates.Separator))
{
content.Add(new Separator()); // no need to pass item, as it's just static HR tag.
}
}
}
return content;
}
}
What we see above is just an iterator that instantiates an object of specific type and saves it into list. Let's then take a look on Paragraph class as an example of such a class, that being instantiated:
public class Paragraph : BaseGenericControl
{
public Paragraph(Item item) : base(item)
{
}
public override string Render()
{
return string.Format("<p class='{0}'>{1}</p>", CssClass, Value);
}
}
The CSHTML view for that rendering seems to be very minimal - it accepts list of dynamics as the model and passes it into HTML helper method:@model List<dynamic>
@Html.Sitecore().RenderGenericControls(Model)
RenderGenericControls helper just iterates all our primitive types in dynamic list and calls render for each of them (again, you would probably be strict on types and use interfaces instead of dynamics):
public static HtmlString RenderGenericControls(this SitecoreHelper html, dynamic content)
{
var htmlBuilder = new StringBuilder();
foreach (dynamic component in content)
{
htmlBuilder.Append(component.Render());
}
return new HtmlString(htmlBuilder.ToString());
}
The order of primitives remains the same as they are kept in Sitecore within container item.
This is not an universal approach for generic content, but still worth of mentioning.