Best practices for Grails Asset layout structure for beginners

Posted By : Ravi Kumar | 24-Jul-2014

In this blog we will see how to set up our layout structure and all assets on using Grails Asset Pipeline plugin. Sometimes a better layout structure also depends on the project and its hierarchy.

 
Summary
 
The asset-pipeline plugin will be used to optimize the use of static web assets.

For performance reasons, we want to minimize the number of css and js files we include and we want to put css in the <head> and as much js as possible at the end of the <body> (just before </body>)...

 

If our application has a main page (home page that initially loads), it would be very nice if we use single css and js file.

 

 

The overall strategy is for the home page to only load a single js file and a single css file with the minimum js and css that is necessary for the Home Page.  Home Page load time with an empty browser cache is critical.  For this reason we should only include Home Page css/js that is essential for the Home Page.

 

 

For all other pages we should include the home-page assets (these can be considered global includes), and a single css and js file for anything specific to the page.  If there is nothing specific to the page, then the global includes would be enough.

Any css or js that is currently inlined directly into page html should be factored out into the page specific asset.

The global home-page assets can be included efficiently on every page since the browser will have them cached (after hitting the home page).

 

Single page Application :

A single application css and js asset file include (in the layout) is sufficient for the single page apps :

for example : admin applications that serves for back-end processes and supports.

<head>
    <asset:stylesheet href="application.css"/>
    <asset:javascript src="application.js"/>
</head>

*** Now we will see, how to define layouts for main application that is for public users i.e. Front-end application.

 

Only as much css that is needed by the Home Page (main page that initialize first) should be in application.css.  The manifest can include any number of css files that are necessary (however we want to organize the css).

eg. :

application.css

/*
*= encoding UTF-8
*= require layout
*= require_tree banner
*= require carousel
*= require sidebar.zoom
*= require quickshop
*= require_full_tree plugins
*= require ...
*/
 
... inline css ....

 

application.js

As much of the JS that can run successfully from the bottom of the page should be included in the <page>.js file.

So for application.js we want to ideally put all the JS and includes that are needed by the Home Page (but only as much as is required by the Home Page).

 

Putting the js at the bottom of the page will help speed up the rendering of the page.

By only including what is needed for the home page, we minimize the size (optimizing performance) when the user visits their first page (normally the home page).

 

eg. :

 

application.footer.js

//= require jquery
//= require_tree banner
//= require_full_tree plugins
//= require carousel
//= require sidebar.zoom
//= require quickshop
//= require...
 
...inline js ....

application.header.js

In some cases page load rendering may rely on some JS.  Placing JS that is required to load the page at the bottom can possibly cause rendering to break.

JS that must be loaded early should be placed in application.header.js.

So for application.header.js want to ideally put all the JS and includes that must be loaded early by the Home Page (but only as much as is required by the Home Page header).

 

eg. :

 

 

application.header.js

//= require jquery
 
...inline js ....

Note 1 : If load rendering breaks because of moving JS to the footer, we should first try and see if redesign of the JS can allow it to move to the footer.

eg. The Home Page Banner could be designed to show the fist frame as a static image without requiring any JS during page load time. Maybe a GSP tag could help generate the markup for this.

Note 2 : If a header.js file includes a js file, then the same file should not be included by the footer.js

 

Layout

We will include the application.* assets on all pages, since after the initial homepage load, all other pages will be able to load these files for almost-free (since it will be in the browser cache).

Since the application assets will be included by all Pages, we can then include them in the Layout.

layout.gsp

<html>
   <head>
      <title><g:layoutTitle/></title>
      <asset:stylesheet src="application.css"/>
      <g:layoutHead/>
      <asset:javascript src="application.header.js"/> 
   </head>
   <body>
      <div class="thecontent">
         <g:layoutBody/>
      </div>
      <asset:javascript src="application.js"/>
   </body>
</html>

Page Specific Assets

Some pages will require more that what is required in the application.* asset files.

We could create page specific asset includes for these extra dependencies, but to simplify maintenance we will store all non-homepage dependencies in a single set of additional files.

 

The following set of files should include all assets that are not needed for the homepage, but are needed for some non-homepages.

  • application2.css

  • application2.js

  • application2.head.js

We can conditionally include these in the main layout if we are not rendering a "landing page".

The layout is enhanced below to conditionally include the application2.* assets if the page does not have a meta tag with "isLanding" true.

 

layout.gsp

<html>
   <g:set var="isLandingPage" value="${pageProperty(name:'meta.isLanding').toBoolean()}" />
   <head>
      <title>modelcitizen - <g:layoutTitle/></title>
      <g:layoutHead/>
 
      <%-- header assets --%>
      <asset:stylesheet src="application.css"/>
      <asset:javascript src="application.header.js"/>
      <g:if test=${!isLandingPage}>
          <asset:stylesheetsrc="application2.css"/>
          <asset:javascript src="application2.header.js"/> 
      </g:if>
 
   </head>
   <body>
      <div class="bodyContent">
         <g:layoutBody/>
      </div>
 
      <%-- footer assets --%>
      <asset:javascript src="application.js"/>
      <g:if test=${!isLandingPage}>
          <asset:stylesheetsrc="application2.js"/>
      </g:if>
   </body>
</html>

Body Content Pages

None of the view content gsp pages will include any css or js since that will be the responsibility of the Layout.

The Home Page must add the landing page meta tag to prevent including the application2.* assets.

 

home.gsp

 

<html>
   <head>
      <title>home</title>
      <!-- include this tag on the Home page, or any other page considered to be a landing page. -->
      <meta name="isLanding" content="true" />
   </head>
   <body>
      ...
   </body>
</html>

 

Deferred Loading of Footer JS

Later we can also experiment to see if there is additional benefit to using deferred loading of the footer application.js assets.

 

*** Now we will see layout for other pages :

Let suppose we have a checkout page and its other process pages.

Checkout Header/Footer

The header and footer is specialized during checkout.

We could create a specialized Layout for this case, but to keep things more DRY, lets stick with a single main layout and we can use similar metadata similar to "isLayout" to signal which header/footer that the layout should render.

Lets introduce a new meta tag for specialized "workflows".  We will start with the "checkout" workflow.  All checkout pages should include this meta data.

Here is an example of how a checkout page should indicate how it is a member of the "checkout" workflow.Checkout View

checkout.gsp

<html>
   <head>
      <title>checkout</title>
      <!-- include this tag on the Home page, or any other page considered to be a landing page. -->
      <meta name="workflow" content="checkout" />
   </head>
   <body>
      ...
   </body>
</html>

The layout should read the current workflow and include generic header/footer templates.

Layout Considering Workflow

layout.gsp

<html>
   <g:set var="isLandingPage" value="${pageProperty(name:'meta.isLanding').toBoolean()}" />
   <g:set var="workflow" value="${pageProperty(name:'meta.workflow')}" />
   <head>
      <title>modelcitizen - <g:layoutTitle/></title>
      <g:layoutHead/>
 
      <%-- header assets --%>
      <asset:stylesheet src="application.css"/>
      <asset:javascript src="application.header.js"/>
      <g:if test=${!isLandingPage}>
          <asset:stylesheetsrc="application2.css"/>
          <asset:javascript src="application2.header.js"/> 
      </g:if>
 
   </head>
   <body>
      <%-- header template --%>
      <g:render template="header" />
      <div class="bodyContent">
         <g:layoutBody/>
      </div>
 
      <%-- footer template --%>
      <g:render template="footer" />
 
      <%-- footer assets --%>
      <asset:javascript src="application.js"/>
      <g:if test=${!isLandingPage}>
          <asset:stylesheetsrc="application2.js"/>
      </g:if>
   </body>
</html>

Template _header.gsp

 

Header

<g:if test="${workflow == 'checkout'}">
    <g:render template="mainHeader.jsp" />
</g:if>
<g:else>
    <g:render template="checkoutHeader.jsp" />
</g:else>

Template _footer.gsp

 

Footer

<g:if test="${workflow == 'checkout'}">
    <g:render template="mainFooter.jsp" />
</g:if>
<g:else>
    <g:render template="checkoutFooter.jsp" />
</g:else>

 

I hope you found some important content here. Thanks for reading.

Ravi Kumar

About Author

Author Image
Ravi Kumar

Ravi is a creative UI designer with experience and capabilities to build compelling UI designs for Web and Mobile Applications. Ravi likes Tech Quizes and playing Football.

Request for Proposal

Name is required

Comment is required

Sending message..