< Return to Feed
Scott Reinhardt - 07.13.2017

Do Not Repeat Yourself

One of the main principles of software development is “Don’t Repeat Yourself,” or DRY.  Yet when working with CrownPeak, many people will consistently repeat the boilerplate in their input templates.  When you take a look, throughout the whole set of input templates you’ll see something like this below, where it’s copy / pasted throughout to maintain consistent structure.


 
Input.StartTabbedPanel("Page Content", "Meta Information", "Page Properties");
    //page content input goes here
Input.NextTabbedPanel();
    InputHelper.ShowMetaInput();
Input.NextTabbedPanel();
    //page properties go here
Input.EndTabbedPanel();

This violates DRY and if the developer was asked, “On all pages, can you swap the meta input tab and the Page Properties tab order?” you’d have to go into every template to move around the tabs.  Note that the meta input has done something to help with this in that it’s been pulled out into a method to keep consistency.  Wouldn’t it be nice if you could have a default template that has those three tabs, would be easy to add your input methods to, and could be extended?  Something where you could have some baseline pieces then add any page-specific pieces as needed? Here’s what my implementation ended up looking like on the input template end.

var defaultTemplate = new DefaultTemplate();
var content = defaultTemplate.Panels[Content.Name]; content.Add(() => {      Input.ShowTextBox("Add page specific content here", "data");      Input.ShowTextBox("Easy to add multiple lines in one action", "multiple"); });
defaultTemplate.Write();

So to make this work I broke down a template into two main pieces:  Panels and Templates.  A template is composed of multiple panels, as in the example below.

public abstract class Panel
{
    public static string Name = "";
    private List<Action> _actions { get; set; }
 
    protected Panel()
    {
        _actions = new List<Action>();
    }
 
    public void Write()
    {
        foreach (var action in _actions)
        {
            action();
        }
    }
 
    public bool HasActions
    {
        get { return _actions.Any(); }
    }
 
    public void Add(Action action)
    {
        _actions.Add(action);
    }
}

And here is an example panel for meta information:


public class MetaData : Panel
{
	public new static string Name = "Meta Data";
 
	public MetaData()
	{
		Add(() => InputHelper.ShowMetaInput());
	}
}

Pretty simple concept here — your input panel has a list of actions that, when you write out the panel, all get called. You can customize with non-default actions by calling Add().

The template follows a similar concept:

public abstract class Template
{
    public Dictionary<string,Panel> Panels { get; set; }
 
    protected Template()
    {
        Panels = new Dictionary<string,Panel>();
    }
 
    public void Write()
    {
        var panelsWithContent = Panels.Where(p => p.Value.HasActions);
        var headers = panelsWithContent.Select(p => p.Key).ToArray();
 
        if (!headers.Any())
        {
            return;
        }
 
        Input.StartTabbedPanel(headers);
        for (int i = 0; i < headers.Length - 1; i++)
        {
            Panels[headers[i]].Write();
            Input.NextTabbedPanel();
        }
        Panels[headers.Last()].Write();
        Input.NextTabbedPanel();
    }
}


All of the panels are accessed by their name so you can grab the correct panel to modify as-needed.

public class DefaultTemplate : Template
{
    public DefaultTemplate()
    {
        Panels.Add(Content.Name, new Content());
        Panels.Add(MetaData.Name, new MetaData());
        Panels.Add(PageProperties.Name, new PageProperties());
    }
}

With our default template looking like the above, the case of changing the meta tab and page properties tab across all pages is made trivial by adding an order property and sorting on it.
 

The other nice part about this is you now have page inheritance.  So, if you have your baseline default template but then have several with a right rail, you could create a right rail panel:

public class RightRail : Panel
{
    public new static string Name = "Right Rail";
 
    public RightRail()
    {
        Add(() => Input.ShowMessage("Right rail component here"));
    }
}
 
 

Then add it on each of the pages that need it.  But that again violates DRY.  So why not create a right rail template?
 

public class RightRailTemplate : DefaultTemplate
{
    public RightRailTemplate()
    {
        Panels.Add(RightRail.Name, new RightRail());
    }
}

As you can see, this cleans up a lot of the boilerplate and makes the templates fall more in line with object-oriented programming principles.