Search code examples
asp.netasp.net-mvcrazorasp.net-mvc-5razor-2

How to make your views DRY with MVC5 and multiple page breakpoints?


I have a predicament that I am not quite sure how to overcome. I do not know what is the right way. I am building a website and I was given a template to integrate with my server code. The problem lies in how the template is outlined. Let me show you an example.

<body>
    <div class="breakpoint active" id="bp_infinity" data-min-width="588">
        <div id="header">full page header content</div>
        <div id="body">some stuff</div>
        <div id="footer">some stuff</div>
    </div>
    <div class="breakpoint" id="bp_587" data-min-width="493" data-max-width="587">
        <div id="header">mobile header content</div>
        <div id="body">some stuff</div>
        <div id="footer">some stuff</div>
    </div>
    <div class="breakpoint" id="bp_492" data-max-width="492">
        <div id="header">mobile header content</div>
        <div id="body">some stuff</div>
        <div id="footer">some stuff</div>
    </div>
</body>

I am trying to setup my MVC5 Views in a way that does not repeats common code. The problem that I am facing is that the header and footer div are common code from page to page and the body changes. The second problem is that each page has different number of breakpoints. Here is a second page to show what I mean:

<body>
    <div class="breakpoint active" id="bp_infinity" data-min-width="588">
        <div id="header">full page header content</div>
        <div id="body">some stuff</div>
        <div id="footer">some stuff</div>
    </div>
    <div class="breakpoint" id="bp_587" data-max-width="587">
        <div id="header">mobile header content</div>
        <div id="body">some stuff</div>
        <div id="footer">some stuff</div>
    </div>
</body>

So the Layout page is now tricky to setup because I can't just say:

<body>
    @RenderBody
</body>

One of the solutions I thought of was to use Sections, something like this:

<body>
    @RenderBody
    @RenderSection("Breakpoint-1", false)
    @RenderSection("Breakpoint-2", false)
    @RenderSection("Breakpoint-3", false)
</body>

Now each page would be along the lines of:

@section Breakpoint-1
{
    <div class="breakpoint active" id="bp_infinity" data-min-width="588">
        @{ Html.RenderPartial("full-page-header"); }
        @{ Html.RenderPartial("full-page-body"); }
        @{ Html.RenderPartial("full-page-footer"); }
    </div>
}
@section Breakpoint-2
{
    <div class="breakpoint" id="bp_587" data-max-width="587">
        @{ Html.RenderPartial("mobile-page-header"); }
        @{ Html.RenderPartial("mobile-page-body"); }
        @{ Html.RenderPartial("mobile-page-footer"); }
    </div>
}

A problem that I see with above code is that if the header now needs to have 5 breakpoints instead of 2, I need to go and modify it everywhere.

Is there a better way to do this? Is what I thought of the best solution for my scenario?

EDIT: To clarify. There are multiple brakpoints in the HTML because only one of them is active at a time. When page hits a certain width, 1 the currenct active breakpoint gets hidden and the new one becomes visible.


Solution

  • Assumptions

    ... are the mother of all....

    1. "some stuff" that goes in the body tag is HTML being fed from some data source, or is hard-coded
    2. "...the header and footer div are common code from page to page..." means that literally, you don't need to change the header/footer at all. (You still could, but I'm ignoring that for now)
    3. The div id's "header", "body", "footer" should be handled as dom classes rather than dom ids. That is another discussion, but ids should always be unique.

    Solution

    This is a basic example, there are plenty of other approaches to try and plenty of other tweaks you can make

    Controller Let's call this BreakpointController

    public ActionResult Index()
    {
      var model = new List<BreakpointViewModel>();
      // populate model
      return View(model);
    }
    

    ViewModel

    public class BreakpointViewModel
    {
        public string BreakPointId { get; set; }
        public int? MinWidth { get; set; }
        public int? MaxWidth { get; set; }
        public string Body { get; set; }
        public bool IsActive { get; set; }
    }
    

    View This should be your index.cshtml (or whatever you want to call it)

    @model IEnumerable<WebApplication1.Models.BreakpointViewModel>
    
    <div>
        <h1>A header!</h1>
    </div>
    
    @Html.DisplayForModel()
    
    <div>
        <h4>A footer!</h4>
    </div>
    

    DisplayTemplate * Thou shalt live in the folder containing views for the controller (or Shared) * Thou shalt live in a subfolder named 'DisplayTemplates' * Thou shalt be named {ModelName}.cshtml

    in the end, the folder structure should look something like this:

    Views
    |-- Breakpoint
    |   |-- DisplayTemplates
    |   |   +-- BreakpointViewModel.cshtml
    |   +-- Index.cshtml
    

    And BreakpointViewModel.cshtml should look like this:

    @model WebApplication1.Models.BreakpointViewModel
    
    <div class="breakpoint @(Model.IsActive ? "active" : null)"
         id="@Model.BreakPointId"
         @(Model.MinWidth.HasValue ? "data-min-width='" + Model.MinWidth + "'" : null)
         @(Model.MaxWidth.HasValue ? "data-max-width='" + Model.MaxWidth + "'" : null)>
         @Html.Raw(Model.Body)
    </div>
    

    Note the minwidth/maxwidth lines in the div. Not required, just how I would personally deal with the widths.

    Resulting HTML

    <div>
        <h1>A header!</h1>
    </div>
    
    <div class="breakpoint active"
            id="bp_1"
            data-max-width=&#39;720&#39;>
        <div>Hello World!</div>
    </div>
    
    <div class="breakpoint"
            id="bp_2"
            data-max-width=&#39;720&#39;>
        <div>Another Breakpoint</div>
    </div>
    
    <div class="breakpoint"
            id="bp_3"
            data-max-width=&#39;720&#39;>
        <div>Third Breakpoint</div>
    </div>
    
    <div class="breakpoint"
            id="bp_4"
            data-max-width=&#39;720&#39;>
        <div>Fourth Breakpoint</div>
    </div>
    
    <div>
        <h4>A footer!</h4>
    </div>
    

    Original Answer

    DisplayTemplates are your friend. If your sections are going to be the same, you can put the relevant information into a ViewModel, then pass the List<ViewModel> to the DisplayTemplate. The MVC engine will then use the DisplayTemplate for your ViewModel to fill out the needed code for each section.

    You only need code your DisplayTemplate for your ViewModel once.

    I don't have any sample code up at the moment, but if you need further help, comment on this and I'll break some out over the weekend.