When defining your grid layouts in Umbraco there are a number of configuration options available that allow you to present your users with preset options to set attributes such as class, data-val or styles. Whilst working on a site recently I wanted to allow the users to set multiple classes on a single row so I created the following settings configuration.
[
{
"label": "Row Colour",
"description": "Set a css class",
"key": "class",
"view": "dropdownlist",
"applyTo": "row",
"prevalues": [
"contrast",
"complementary"
]
},
{
"label": "Row Padding",
"description": "Set a css class",
"key": "class",
"view": "dropdownlist",
"applyTo": "row",
"prevalues": [
"small",
"medium",
"large"
]
}
]
All good? Unfortunately not! When it came to testing it only the second class was being rendered on the page. It turns out that each setting has to have a unique key value, if you have two settings with the same key only the last one will be captured when you save your page. It makes sense when you think about it but knowing it wasn't possible was no help in my situation. One solution would be to combine the two settings in to one with each combination of the two classes. In this case that wouldn't be too bad but I couldn't guarantee that these are the only classes I'd want to apply, chuck in a couple more classes and you've now got a very long list!
To get around this I updated the configuration so that duplicate keys are appended with [n], as shown below. This allows me to capture multiple classes when I'm creating pages using the grid but it has one major flaw!
[
{
"label": "Row Colour",
"description": "Set a css class",
"key": "class",
"view": "dropdownlist",
"applyTo": "row",
"prevalues": [
"contrast",
"complementary"
]
},
{
"label": "Row Padding",
"description": "Set a css class",
"key": "class",
"view": "dropdownlist",
"applyTo": "row",
"prevalues": [
"small",
"medium",
"large"
]
}
]
With each setting now having a unique key both classes will be captured when you save the page. However, as they've now got unique keys the attributes won't actually be combined so you end up with the following rendered output.
<div class="contrast" class[2]="medium">
To overcome this issue we need to refactor the function that renders the HTML attributes of your grid so that value of matching keys are grouped together and rendered as a single attribute. This is done by iterating over the config properties and adding the key value pairs to a dictionary. If the key already exists in the dictionary then the value is appended to the existing dictionary item or, if not, a new dictionary item is created. We then iterate over the dictionary to create our attribute string and voila! multiple attributes are now possible.
public static MvcHtmlString RenderElementAttributes(dynamic contentItem)
{
var attrs = new List<string>();
JObject cfg = contentItem.config;
if (cfg != null)
{
var properties = new Dictionary<string, string>();
foreach (var property in cfg.Properties())
{
var name = Regex.Match(property.Name, "\\[[0-9]+\\]").Success ? property.Name.Substring(0, property.Name.IndexOf('[')) : property.Name;
if (properties.ContainsKey(name))
{
properties[name] += " " + property.Value.ToString();
}
else
{
properties.Add(name, property.Value.ToString());
}
}
if (properties.Any())
{
foreach (var property in properties)
{
attrs.Add(property.Key + "='" + property.Value + "'");
}
}
}
JObject style = contentItem.styles;
if (style != null)
{
var cssVals = new List<string>();
foreach (JProperty property in style.Properties())
{
cssVals.Add(property.Name + ":" + property.Value.ToString() + ";");
}
if (cssVals.Any())
{
attrs.Add("style='" + string.Join(" ", cssVals) + "'");
}
}
return new MvcHtmlString(string.Join(" ", attrs));
}
This is a relatively small change to how your grids are rendered but provides far simpler customisation possibilities than creating settings for each possible combination of classes and gives your users greater flexibility in how they can customise their pages.