Why widgets?
One of the best things about Umbraco is that there are so many ways to build websites, and one of the worst things about Umbraco… is that there are so many ways to build websites. It can be overwhelming to learn the seemingly endless approaches. For that reason, I will just be covering one technique that I think the vast majority of Umbraco developers will find particularly useful. That is, building Umbraco websites with widgets using Archetype (a tool to create complex content structures) and Ditto (a tool to convert complex content structures in C# classes).
Here are some reasons why you should be building your Umbraco websites with widgets:
- New Pages Quickly. Creating new pages becomes trivially easy (just compose the widgets).
- Flexible Pages. Content editors can experiment by adding or subtracting content (just add or remove the widgets).
- Isolate Styles. Styling becomes very easy to manage (just style the widget and gaps between widgets).
- Avoid Redundancy. You no longer need to worry about two developers rebuilding the same component on two different pages (you are building components, not pages).
What are widgets?
In short, widgets are the individual features you see on any given page within a website. A slideshow is a widget, a text block is a widget, an image gallery is a widget, a contact form is a widget, and so on. People use different terminology for this concept (e.g., components, features, macros, etc.), and I use the term widget. I recommend against using the term "macro" in particular, as it would be easy to confuse with the Umbraco feature called macros (i.e., rich text macros and grid macros).
Some Example Widgets
Before I get too deep into the details, here are some examples of widgets.
Those should give you some sense of what a widget is. This article is about building those types of widgets with Archetype.
Archetype Fieldsets as Widgets
Archetype is a natural choice to build widgets. It offers a few key features that make this possible:
- Lists of heterogeneous content.
- Ability to nest content within content.
- Adding, removing, disabling, and reordering content.
- Entire interface is inline on the content node.
The lists of heterogeneous content allow you to combine data from various elements (i.e., widgets). For example, this means you can add a slideshow followed by a text block followed by an image gallery. Archetype refers to these blobs of content as "fieldsets", and these fieldsets are what I use to create widgets.
The ability to nest content within content allows for complex widgets (e.g., a slideshow). In the case of a slideshow, you'd have a slideshow widget, and within it would be a number of slides. This also allows for the possibility to create layouts with Archetype as well. For example, you could have a "main content with sidebar" layout, or a "two column" layout, or a default "full width" layout. Each of these layouts would be created as an Archetype fieldset, and each of them would have a property or properties that also contain Archetype fieldsets.
The ability to add, remove, disable, and reorder content make creating and editing content a stress-free experience, as you can experiment along the way and adjust as you go. For example, you might want to add an image gallery to the page, but you may not be sure where it would look best. Archetype allows you to create the image gallery, then reorder it relative to the other widgets by dragging it. If you need to temporarily remove some of the content to make some edits, you can easily do so by disabling it and then later enabling it (i.e., you don't have to delete it, which would require you to enter the content again).
Finally, the fact that the entire interface is inline makes editing the content an extremely quick process (as opposed to an older strategy of nested content nodes as a means of creating widgets). There is no wait to for things to load, as would be the case when navigating from page to page in the nested content node approach.
Use Archetype to Control Layout
In the past, some people have recommended that you find alternatives to Archetype when creating a layout. One reason was that layouts were hard to change after creating them with Archetype. Another reason was that the extra indentation caused by nested Archetypes made the editing experience clumsy on smaller screens. Luckily, there are solution to both of these problems now.
To avoid the indentation that Archetype creates, you can inject your own styles into the back office to reduce the indentation (e.g., by positioning the property editors below the property labels rather than to the right of the labels). Archetype generates classes based on the names of your Archetype fieldsets and properties, which allows you to target those elements with CSS selectors easily. You can read about how to inject CSS into the back office here: http://24days.in/umbraco/2015/umbraco-7-back-office-tweaks/
In order to change a layout created with Archetype, you can use a new feature called cross-Archetype dragging. This allows you to drag Archetype fieldsets from one Archetype to another Archetype. The other Archetype can even be a nested Archetype (i.e., when an Archetype property exists inside of an Archetype fieldset). This means that if you create a "main content with sidebar" widget that has two properties ("main content" and "sidebar content"), you can drag that content outside of that widget and into another widget (e.g., into a "two column" widget). An example of this cross-Archetype dragging feature is below, and a description of it can be found here: https://github.com/kgiszewski/ArchetypeManual/blob/master/02%20-%20Configuration.md#cross-archetype-dragging
Mapping Archetype Widget Data with Ditto
With the major upgrade from Ditto 0.8 to Ditto 0.9, the Ditto Archetype Resolvers project was broken in a major way and it seemed that mapping Archetype content with Ditto would no longer be possible. However, with a few changes that were included in Ditto 0.10, it is now fully possible to map Archetype widgets with Ditto.
For those unfamiliar with Ditto, it creates instances of C# classes based on your Umbraco content. This makes working with the structure of the content much easier (i.e., you get intellisense and your content structure is fully specified by your classes). Ditto does this with a concept called a "Ditto processor". A processor is essentially a C# attribute that you decorate your classes or properties with that tells Ditto how to convert content into your classes. That looks like this:
// https://gist.github.com/Nicholas-Westby/b33384cc1396d542f964f645b414563a#file-widget-page-cshtml
@{
// Use Ditto's "As" extension method to map the content to a C# class.
var pageModel = Model.Content.As();
// Get the widgets from the page model.
var widgets = pageModel.Widgets;
}
@foreach (var widget in widgets)
{
// Render widgets here.
}
// https://gist.github.com/Nicholas-Westby/b33384cc1396d542f964f645b414563a#file-widgetpage-cs
public class WidgetPage
{
// IWidget is just an empty interface.
// Many widget classes implement the IWidget interface.
// DittoMixedArchetype comes from here: https://github.com/leekelleher/umbraco-ditto-labs/blob/develop/src/Our.Umbraco.Ditto.Archetype/ComponentModel/Processors/DittoMixedArchetypeAttribute.cs
[DittoMixedArchetype]
public IEnumerable MainContent { get; set; }
}
Not All Data Resides in Widgets
For a lot of widgets, it makes sense to store the data for that widget in the Archetype fieldset. For example, a slideshow or an image gallery would likely store the images and text within their respective Archetype fieldsets. However, you may want to consider storing more reusable data outside of the widgets.
One example would be a banner widget. Imagine a banner that appears at the top of each page. This banner might include a breadcrumb of ancestor pages and header text. If you were to store the header text (essentially the title of the page) inside of the banner widget, any page wanting to display that header text would have to parse the Archetype widgets just to extract the header text. This would include pages like the HTML sitemap, which typically automatically generate a tree of all (or most) pages in a site. To make it easier on the HTML sitemap page, it'd probably be best to include a "Header" field on each page rather than forcing the HTML sitemap page to parse out the "Banner" widget on each page to extract the header field from there.
Continuing on with the banner example, one might choose to implement the breadcrumb as a list of links on a property within the breadcrumb Archetype fieldset. However, that causes a couple problems. For one, it adds unnecessary extra work on the content editor (i.e., they have to manually enter every breadcrumb). Instead, the code for the banner widget can just generate the breadcrumb from the ancestor pages. Secondly, this manual approach is prone to causing stale data. That is, an ancestor page could be renamed, and every descendant page would then have an inaccurate breadcrumb.
There are many reasons to store data outside of an Archetype fieldset widget. Rather than go over all of them, I'll leave it to the discretion of the reader. However, here are a few reasons to consider storing some of your data outside of widgets:
- Galleries of items (e.g., an article gallery) can more easily extract data from pages rather than from widgets on pages.
- It is more apparent to content editors that they should fill in a field than it is that they should add a particular widget.
- It is easier to mark a field as mandatory than it is to mark a widget as mandatory.
- Some data is used in multiple places, and it is easier to extract that data from a property on a page rather than from a property on a widget on a page.
Prepopulate Widgets by Document Type
Some people rightly have a concern that a content editor may not know the appropriate widgets to use when creating a given page. If you have 50 widgets, it may not be all that intuitive that 5 in particular should be used for a given type of page. To help manage that, I recommend initializing the widgets in a page based on the document type. You could do that yourself by using the events in the content service, but a colleague of mine has already done that work for you: https://our.umbraco.org/projects/backoffice-extensions/contenttemplates/
The documentation is a bit light, but the basic idea is that you can create a part of the content tree that indicates default widgets. Essentially, you create a "content template" (not to be confused with Umbraco templates, which is an entirely different concept) as a content node with the default widgets, and you select the document types this content template applies to. When you create a content node with one of the selected document types, it will get created with the widgets specified on the content template node.
Styling Widgets
Styling widget-based websites can become a challenge when you consider that any widget can appear next to any other widget. However, you don't need to create styles for each of those combinations.
All you need to do is create a default margin so that widgets get a sensible spacing between them. If you need a specific margin between two widgets, you can use a CSS sibling selector to override the default margin. This is also the reason I recommend you use the top margin as your default (otherwise, changing the margin between widgets becomes more difficult to achieve with CSS alone).
There are a couple more situations you'll need to consider when styling widgets. One is the margin at the top and bottom of the page (the gap separating the widgets from the header and footer). Rather than setting the margin on the container of the widgets, you can set the margin on the first and last widget using the first-child and last-child CSS selectors. If particular widgets need a different margin (e.g., if there should be no gap between them and the header/footer), you can use the first-child and last-child CSS selectors in combination with the class name attached to those particular widgets.
Another situation I've come across is when showing widgets in a sidebar. Sometimes the widgets in the sidebar need to be displayed a little differently than they would in the main content area. For example, you might want to reduce the spacing between widgets. Rather than styling each of the widgets that happens to appear in the sidebar with a particular margin, you can set a default margin for all widgets that appear in the sidebar. This is again a default you can override using a CSS sibling selector for two particular widgets that appear adjacent to one another.
Here are a few examples that show how to implement some of the above mentioned styles:
// https://gist.github.com/Nicholas-Westby/3211e7f956d94e5656a700c7cdd53b10
/* Default widget gap. */
.widget {
margin-top: 30px;
}
/* Override widget gap between slideshow and callout. */
.slideshow-widget + .callout-widget {
margin-top: 0px;
}
/* Gap at top and bottom of page. */
.widget:first-child {
margin-top: 50px;
}
.widget:last-child {
margin-bottom: 60px;
}
/* Default sidebar widget gap. */
.sidebar .widget {
margin-top: 10px;
}
/* Override sidebar widget gap between video and rich text. */
.sidebar .video-widget + .rich-text-widget {
margin-top: 0px;
}
Putting it All Together
Now that you've read about building widget-based websites with Archetype, here's how you can put it all together:
- Fieldsets. Create an Archetype data type with a fieldset for each widget you want to use, then create document types with a property based on that data type.
- Single View. Avoid creating a Razor view for each type of page in your website. Instead, create a content template with the default widgets for each document type, and use a single Razor view to render all of them.
- Map to Classes. Map your Archetype widgets to C# classes, then loop over each of them to render them to markup.
- Style. Create a few default styles to ensure your widgets look good next to each other, then refine those styles when you need to override the defaults.
- Reuse. When you nee to create a new type of page, reuse your existing widgets to save a ton of time.
That's pretty much all there is to widgets. There are lots of ways of building them, but this approach should be a good start. If you have any questions, you can let me know in the comments or contact me on Twitter.
Resources
Here are a few resources that will help you create widget-based websites with Archetype.