Master Layouts

Preface

This entire subject is ASP.NET MVC specific. MonoRail has it's own mechanism for selecting layout files, and Spark works in that context as expected.

How master-layout files work

When a view is rendered with a master layout template selected Spark renders the view in a multiple passes. The first pass will render the contents of the view template from the top down. The output of that pass is captured in a content variable named "view".

The second pass will render the contents of the master template from the top down. The master template will contain <use content="view"/> or <use:view/> at the location where the view content should appear.

Application.spark

<html>
  <head>
    <title>${Title}</title>
  </head>
  <body>
    <div id="header">dot dot dot</div>
    <div id="pageContent"><use content="view"/></div>
    <div id="footer">dot dot dot</div>
  </body>
</html>

From the master template's perpective the "view" is just a piece of named content.

Selecting Layout

There are several ways to select a which master layout file should be used. The following are the ways a template may be selected, from weakest to strongest.

  • An Application.spark file in the Views/Layouts folder or Views/Shared folder

This is the most general-purpose way to have a site-wide master template. It will not be used if the controller returns a PartialView().

  • A .spark file in Views/Layouts or Views/Shared with the same name as the controller

For example if you have an AccountController you could have a Views/Layouts/Account.spark file which is used on that controller, but all other controllers use the Views/Layouts/Application.spark template.

  • Naming the master layout as the second argument when you return a View() as the ActionResult

This gives the selection of layout to the controller, which some people may believe isn't necessarily a concern for the controller. What'cha gonna do? If this is present it will override the first two conventions.

  • Naming the master layout as an <use master=""/> element in the view.

This is actually the strongest mechanism available for wrapping a view in a layout file. It will override the conventional forms of master selection, and it will cause the name of the master in the View() ActionResult to be ignored if it's present.

Interestingly, it may also be used in the layout files themselves to establish three or more pass rendering.

Three pass rendering

Three pass rendering can be used to address a common problem. The cause isn't always obvious, especially if you're new to the named-content top-down rendering technique.

The problem is this. Let's say you're using a named content section, like "head" or "nav", at the top of your master layout file. Then you add some partials in the sidebar or other places in the layout that add script and stylesheet references to the "head" content. The problem you'll encounter is that the "head" is actually written to the output stream before those bits further down the layout execute and add those resources to the named content.

Views/Layouts/Application.spark

<html>
  <head>
    <use content="head"/>
  </head>
  <body>
    <Header/>
    <use content="view"/>
    <Sidebar/>
    <Footer/>
    <use content="tail"/>
  </body>
</html>

Views/Shared/_Sidebar.spark

<content name="head">
  <script src="~/content/js/jquery-ui-accordian.js" once="jquery-accordian"></script>
</content>
<div id="sidebar">etc</div>

One way to use three-pass rendering to solve this problem would be to create a super-layout named Html.spark, and for the master file Application.spark to wrap itself in that layout. Note that from the Html.spark file's perspective the entire output of the Application.spark, and the view which it encloses, is contained in the "view" named content.

Views/Layouts/Html.spark

<html>
  <head>
    <use content="head"/>
  </head>
  <body>
    <use content="view"/>
    <use content="tail"/>
  </body>
</html>

Views/Layouts/Application.spark

<use master="Html"/>
 
<Header/>
<use content="view"/>
<Sidebar/>
<Footer/>