Hello there, as everyone already knows, webforms development in .NET is going to die one day. Therefore, we shall embrace MVC. So during the past weeks I have been putting my hands (and head) on the MVC world and I would like to share with you a Sitecore 8.1 implementation of the Zurb Foundation grid based front-end framework. I am using version 5.5.3, you can download it from this link.
The first thing to understand is that this is a grid made of rows and columns. Each row can have up to 12 columns. Each column can have different sizes defined for small, medium and large devices (smartphones, tablets and laptops/desktops). A basic HTML markup for rows and columns would look like this:
So, to get this framework implemented we need a few things:
- Dynamic placeholders
- Define templates
- Define and code our main layout
- Define and code renderings
- Define and configure placeholders
1. Dynamic placeholders
Sitecore 8.1 still doesn’t have native support for dynamic placeholders, therefore I am basing my implementation on the approach recommended on this answer from Stack Overflow. It is not exactly the same code since I have changed the way it defines whether the placeholder is dynamic or not by placing a “_dph_” string on its key and made a few modifications to make it work fine with my Sitecore 8.1 instance.
Dynamic placeholders are required so we can dynamically add as many rows and cols we want in a page, each one with its own placeholder with a shared key configured to accept specific renderings and also its own dynamically generated individual key to make sure the renderings are placed on the right placeholder.
For that we need 3 classes here:
- GetDynamicKeyAllowedRendering.cs
- GetDynamicKeyPlaceholderChromeData.cs
- RazorExtension.cs
GetDynamicKeyAllowedRendering.cs
using Sitecore;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Pipelines.GetPlaceholderRenderings;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
namespace Sitecore81Foundation.SitecoreExtensions.Pipelines.DynamicKeyPlaceholder
{
public class GetDynamicKeyAllowedRenderings : GetAllowedRenderings
{
public new void Process(GetPlaceholderRenderingsArgs args)
{
string placeholderKey = args.PlaceholderKey;
if (placeholderKey.Contains("_dph_"))
{
placeholderKey = placeholderKey.Substring(0, placeholderKey.LastIndexOf("_dph_"));
if (placeholderKey.LastIndexOf("/") >= 0)
{
placeholderKey = placeholderKey.Substring(placeholderKey.LastIndexOf("/") + 1);
}
}
else
{
return;
}
Assert.IsNotNull((object)args, "args");
Item placeholderItem = (Item)null;
if (ID.IsNullOrEmpty(args.DeviceId))
{
placeholderItem = Client.Page.GetPlaceholderItem(placeholderKey, args.ContentDatabase, args.LayoutDefinition);
}
else
{
using (new DeviceSwitcher(args.DeviceId, args.ContentDatabase))
placeholderItem = Client.Page.GetPlaceholderItem(placeholderKey, args.ContentDatabase, args.LayoutDefinition);
}
List<Item> list = (List<Item>)null;
if (placeholderItem != null)
{
args.HasPlaceholderSettings = true;
bool allowedControlsSpecified;
list = this.GetRenderings(placeholderItem, out allowedControlsSpecified);
if (allowedControlsSpecified)
{
args.CustomData["allowedControlsSpecified"] = true;
args.Options.ShowTree = false;
}
}
if (list != null)
{
if (args.PlaceholderRenderings == null)
{
args.PlaceholderRenderings = new List<Item>();
}
args.PlaceholderRenderings.AddRange(list);
}
}
}
}
GetDynamicKeyPlaceholderChromeData.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sitecore.Pipelines.GetChromeData;
using System.Text.RegularExpressions;
using Sitecore.Diagnostics;
using Sitecore81Foundation.SitecoreExtensions.Pipelines.DynamicKeyPlaceholder;
using Sitecore.Data.Items;
using Sitecore.Web.UI.PageModes;
namespace Sitecore81Foundation.SitecoreExtensions.Pipelines.GetChromeData
{
public class GetDynamicKeyPlaceholderChromeData : GetPlaceholderChromeData
{
public override void Process(GetChromeDataArgs args)
{
Assert.ArgumentNotNull(args, "args");
Assert.IsNotNull(args.ChromeData, "Chrome Data");
if ("placeholder".Equals(args.ChromeType, StringComparison.OrdinalIgnoreCase))
{
string placeholderKey = args.CustomData["placeHolderKey"] as string;
// Is a Dynamic Placeholder
if (placeholderKey.Contains("_dph_"))
{
placeholderKey = placeholderKey.Substring(0, placeholderKey.LastIndexOf("_dph_"));
if (placeholderKey.LastIndexOf("/") >= 0)
{
placeholderKey = placeholderKey.Substring(placeholderKey.LastIndexOf("/") + 1);
}
}
args.ChromeData.DisplayName = placeholderKey;
args.ChromeData.ExpandedDisplayName = placeholderKey + " Dynamic Placeholder";
}
}
}
}
RazorExtension.cs
using Sitecore.Mvc.Presentation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Sitecore81Foundation.SitecoreExtensions.DynamicKeyPlaceholder
{
public static class RazorExtension
{
public static HtmlString DynamicPlaceholder(this Sitecore.Mvc.Helpers.SitecoreHelper helper, string dynamicKey)
{
var currentRenderingId = RenderingContext.Current.Rendering.UniqueId;
return helper.Placeholder(string.Format("{0}_dph_{1}", dynamicKey, currentRenderingId));
}
}
}
2. Define templates
Assuming we want to organize our implementation for multi-sites we will need a few templates:
Template |
Description |
Site |
to organize and configure our sites in the content tree |
Home |
for the home page item |
Col |
Rendering parameters template for the Column rendering (to define its size) |
For now Site and Home will have no fields. The fields for Col are displayed below:
Field Name |
Type |
Source |
Small Devices |
Droplist |
/sitecore/content/SFoundation Configuration/Lists/Columns Small Devices |
Medium Devices |
Droplist |
/sitecore/content/SFoundation Configuration/Lists/Columns Medium Devices |
Large Devices |
Droplist |
/sitecore/content/SFoundation Configuration/Lists/Columns Large Devices |
It is important to remember that as rendering parameters template, Col needs to have Standard Rendering Parameters as its base template. This base template is located at /sitecore/templates/System/Layout/Rendering Parameters.
3. Define and code our main layout
Our main layout will make use of Foundation css and js files. We could (and probably should) later add them to a bundle and use @Scripts.Render and @Styles.Render, but for now we will just reference them the classic way.
So this is our main layout:
MainFoundationLayout.cshtml
@using Sitecore.Mvc
@using Sitecore.Mvc.Analytics.Extensions
@using Sitecore.Mvc.Presentation
@model RenderingModel
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>@Html.Sitecore().Field("title", new { DisableWebEdit = true })</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/css/foundation.css" />
/js/vendor/modernizr.js
@Html.Sitecore().VisitorIdentification()
</head>
<body>
< div>
@Html.Sitecore().Placeholder("main")
</div>
< script src="/js/vendor/jquery.js"></script>
@if (Sitecore.Context.PageMode.IsPageEditorEditing) {
< script>jQuery.noConflict();</script>
}
< script src="/js/foundation.min.js"></script>
< script>
$(document).foundation();
</script>
</body>
</html>
You have noticed that after inserting jquery.js we are verifying whether we are in Page Editor (Experience Editor) mode. To avoid any conflict with Experience Editor’s jQuery we add a jQuery.noConflict() call.
We have one static placeholder called main. And this placeholder should be configured to accept Row renderings once we have it created and configured in Sitecore.
Now we add the reference to our main layout in Sitecore:
4. Define and code renderings
We will write the Row and Col renderings to accept dynamic placeholders. The Col rendering will also read from the rendering parameters to define the size for the row.
When we are in Page Editor mode we also want to add an extra line to identify our row and make it visible to be edited even if it is empty (with not other components added on it). An extra blank line (<br />) is also added at the end of the component to provide space for the Add Here buttons when in Edit mode.
Row.cshtml
@using Sitecore.Mvc
@using Sitecore.Mvc.Presentation
@using Sitecore81Foundation.SitecoreExtensions.DynamicKeyPlaceholder
@model RenderingModel
@{
string s = Sitecore.Mvc.Presentation.RenderingContext.Current.Rendering.Parameters.ToString();
}
< div class="row">
@if (Sitecore.Context.PageMode.IsPageEditorEditing)
{
<br /><label>[row]</label>
}
@Html.Sitecore().DynamicPlaceholder("row")
@if (Sitecore.Context.PageMode.IsPageEditorEditing)
{ <br /> }
</div>
Col.cshtml
@using Sitecore.Mvc
@using Sitecore.Mvc.Presentation
@using Sitecore81Foundation.SitecoreExtensions.DynamicKeyPlaceholder
@model RenderingModel
@{
string small = Sitecore.Mvc.Presentation.RenderingContext.Current.Rendering.Parameters["Small Devices"];
string medium = Sitecore.Mvc.Presentation.RenderingContext.Current.Rendering.Parameters["Medium Devices"];
string large = Sitecore.Mvc.Presentation.RenderingContext.Current.Rendering.Parameters["Large Devices"];
}
< div id="sfColumns" class="@Html.Raw(small) @Html.Raw(medium) @Html.Raw(large) columns">
@if (Sitecore.Context.PageMode.IsPageEditorEditing)
{
<br /><label>[col]</label> @* Required to make the column visible even when empty *@
}
@Html.Sitecore().DynamicPlaceholder("col")
@if (Sitecore.Context.PageMode.IsPageEditorEditing)
{ <br /> }
</div>
In Sitecore I am configuring the rendering items under /sitecore/layout/Renderings/SFoundation/Structure (SFoundation and Structure are two new folders I added).
The Row rendering item will have the following field set:
- Path: /Views/SFoundation/Structure/Row.cshtml (this is the file path to the Row MVC rendering I’ve coded)
The Col rendering item will have the following fields set:
- Path: /Views/SFoundation/Structure/Col.cshtml
- Parameters Template: Templates/SFoundation/Rendering Parameters/Col (this is my path in Sitecore to the Col rendering parameters template I’ve created)
- Open Properties after Add: Yes
5. Define and configure placeholders
Now I want to configure the Placeholder Settings for my main layout Main placeholder, Row and Col. I create a folder for SFoundation and inside the folder I add three placeholders: main, row and col.
Main placeholder key is main and in the Allowed Controls we add the Row rendering.
Row pladeholder key is row and in the Allowed Controls we add the Col rendering.
Col placeholder key is col and in the Allowed Controls we add the Row rendering (allowing for recursively creating rows inside cols and so on).
Final Result
Here we have a screen shot of our content tree:
This is how Columns Large Devices look like (Medium and Small will follow the same pattern):
large-1 to large-12 items have no fields, they are created only to populate the rendering parameters dropdown list.
And this is the result on Experience Editor:
First you get just an empty placeholder:
Once you click Add here you can then add your first Row to the grid.
Once your first row is added you can now add columns to it.
As soon the Col rendering is added it will prompt the user to select the size for the column.
Thanks to the extra spacing added for when on Page Editor mode you can easily see where each Add here button will add a new rendering.
Final result with two rows having two columns each.
On the next post I will share my implementation of the first two Zurb Foundation components: Orbit Slider and Top Bar.