Last updated by 4 years ago

Page: Using ExtJS Layouts with Grails Layouts, Version:1

Using ExtJS Layouts with Grails Layouts

Synopsis

If you're like me, using CSS to manage page layout is a nightmare. One of the many features of the Ext JS library is the ability to programically define page layouts with javascript, no CSS skills required.

It's likely that in your Grails app you will want to define some kind of template that defines your master layout (grails-app/views/layouts/main.gsp) which has a section that other pages fill in. This tutorial will show you one way of achieving this while using Ext for your layout.

This is not an Ext tutorial

I will not be explaining the hows and whys of Ext here. Check the Ext documentation as it is packed with lots of good examples.

Ext layouts

Ext lets you lay the page out in terms of regions. Here is some Ext code to define a layout that has two nested layouts…

contentInnerLayout = new Ext.BorderLayout('content', {
	center: {}
});

headerInnerLayout = new Ext.BorderLayout('header', { center: {margins: {top: 10, left: 5, right: 10, bottom: 10}}, west: {initialSize: 200, margins: {top: 10, left: 10, right: 5, bottom: 10}} });

layout = new Ext.BorderLayout('container', { north: {initialSize: 50}, center: {} });

layout.beginUpdate(); headerInnerLayout.add('center', new Ext.ContentPanel('banner', {fitToFrame: true})); headerInnerLayout.add('west', new Ext.ContentPanel('logo', {fitToFrame: true})); contentInnerLayout.add('center', new Ext.ContentPanel('include', {fitToFrame: true})); layout.add('north', new Ext.NestedLayoutPanel(headerInnerLayout, {fitToFrame: true})); layout.add('center', new Ext.NestedLayoutPanel(contentInnerLayout)); layout.endUpdate();

You will see what that will look like in a little bit. The point is that it lets you define a layout programatically instead of using CSS magic.

Tutorial

Goal

Building on the above code, we want all our pages to have a layout like the picture above, but any page can (optionally) introduce other nested layouts in the include section.

Installing Ext

First of all, you will need to add Ext to your Grails app. I downloaded the Ext 1.1 beta 1 and dropped the whole directory into web-app/js for simplicity.

Our main.gsp file

Our grails-app/views/layouts/main.gsp becomes …

<html>
	<head>
		<title><g:layoutTitle default="Grails" /></title>
		<link rel="stylesheet" href="${createLinkTo(dir:'js',file:'ext-1.1-beta1/resources/css/ext-all.css')}"></link>
		<link rel="stylesheet" href="${createLinkTo(dir:'js',file:'ext-1.1-beta1/resources/css/ytheme-aero.css')}"></link>
		<script type="text/javascript" src="${createLinkTo(dir:'js',file:'ext-1.1-beta1/adapter/ext/ext-base.js')}"></script>
		<script type="text/javascript" src="${createLinkTo(dir:'js',file:'ext-1.1-beta1/ext-all-debug.js')}"></script>
		<script type="text/javascript" charset="utf-8">
			Ext.BLANK_IMAGE_URL = "${createLinkTo(dir:'js',file:'ext-1.1-beta1/resources/images/default/s.gif')}";
		</script>
		<g:javascript library="application" />
		<g:layoutHead />
	</head>
	<body>
		<div id="container">
			<div id="header">
				<div id="logo" style="background-color: red;">logo</div>
				<div id="banner" style="background-color: blue;">banner</div>
			</div>
			<div id="content">
				<div id="include">
					<g:layoutBody />
				</div>
			</div>
		</div>
	</body>
</html>
The first couple of lines are just loading up Ext. Refer to the Ext docs for more info about that. The only important line in the head section is the line ..
<g:javascript library="application" />
This will load the web-app/js/application.js file.

You can see in the body section that we are doing nothing other than defining a simple structure of divs. There is no styling to speak of except for setting the background colour of the divs.

Notice how we are wrapping {{<g:layoutBody />}} in a div with id {{include}}.

Creating an Application class

To make things easier I have created a simple {{Application}} js class that defines my master Ext layout and provides a hook for other pages to modify the layout.

This is my web-app/js/application.js file …

code: null
Application = function() { this.layoutDecorator = null;

this.init = function () { // Our master layout layout = new Ext.BorderLayout('container', { north: {initialSize: 50}, center: {} });

// Our standard header headerInnerLayout = new Ext.BorderLayout('header', { center: {margins: {top: 10, left: 5, right: 10, bottom: 10}}, west: {initialSize: 200, margins: {top: 10, left: 10, right: 5, bottom: 10}} });

// Included content contentInnerLayout = new Ext.BorderLayout('content', { center: {} });

layout.beginUpdate(); headerInnerLayout.add('center', new Ext.ContentPanel('banner', {fitToFrame: true})); headerInnerLayout.add('west', new Ext.ContentPanel('logo', {fitToFrame: true})); contentInnerLayout.add('center', this.getContentInnerPanel()); layout.add('north', new Ext.NestedLayoutPanel(headerInnerLayout, {fitToFrame: true})); layout.add('center', new Ext.NestedLayoutPanel(contentInnerLayout)); layout.endUpdate(); }

this.getContentInnerPanel = function() { if (this.layoutDecorator) { var includeLayout = new Ext.BorderLayout('include'); this.layoutDecorator(includeLayout); return new Ext.NestedLayoutPanel(includeLayout, {fitToFrame:true}); } else { return new Ext.ContentPanel('include', {fitToFrame:true}); } } }

var app = new Application(); Ext.EventManager.onDocumentReady(app.init, app, true);

code: null
Lets have a look at that init function piece by piece …
code: null
// Our master layout layout = new Ext.BorderLayout('container', { north: {initialSize: 50}, center: {} });
code: null
Here we are splitting the page into a top section and a bottom section
code: null
headerInnerLayout = new Ext.BorderLayout('header', { center: {margins: {top: 10, left: 5, right: 10, bottom: 10}}, west: {initialSize: 200, margins: {top: 10, left: 10, right: 5, bottom: 10}} });
code: null
This is a layout we will use for the header section (north part of the master layout). We are splitting it into a 200px wide left section and a section on the right that takes up the rest of the real estate. We have also defined some margins around each section.
code: null
contentInnerLayout = new Ext.BorderLayout('content', { center: {} });
code: null
This will be our include layout. Just one central section.
code: null
layout.beginUpdate(); headerInnerLayout.add('center', new Ext.ContentPanel('banner', {fitToFrame: true})); headerInnerLayout.add('west', new Ext.ContentPanel('logo', {fitToFrame: true})); contentInnerLayout.add('center', this.getContentInnerPanel()); layout.add('north', new Ext.NestedLayoutPanel(headerInnerLayout, {fitToFrame: true})); layout.add('center', new Ext.NestedLayoutPanel(contentInnerLayout)); layout.endUpdate();
code: null
Here we are mapping the content (divs) to the different regions of the layout. Notice how you can either map a {{Ext.ContentPanel}} to a region of a layout or a {{Ext.NestedLayoutPanel}}.

The {{getContentInnerPanel}} function

This function is responsible for returning either a panel of some kind to be rendered into the include section of the layout.

code: null
this.getContentInnerPanel = function() { if (this.layoutDecorator) { var includeLayout = new Ext.BorderLayout('include'); this.layoutDecorator(includeLayout); return new Ext.NestedLayoutPanel(includeLayout, {fitToFrame:true}); } else { return new Ext.ContentPanel('include', {fitToFrame:true}); } }
code: null
Our Application class defines a public field called {{layoutDecorator}} which is expected to be a function. If it is not null, it is passed an instance of {{Ext.BorderLayout}} to augment. We will see an example of this later.

If no layout decorator has been supplied, we simply return a panel of the include section.

The global {{app}} object

Note that we are creating a global instance of {{Application}} and calling it {{app}}

var app = new Application();

An example page

Here is an example on how to insert a nested layout in the include section ....

<html>
	<head>
		<meta name="layout" content="main" />
		<title>Decorated</title>
		<script type="text/javascript" charset="utf-8">

app.layoutDecorator = function(layout) { layout.addRegion('west', {initialSize: 355, margins: {top: 10, left: 10, right: 5, bottom: 10}}); layout.add('west', new Ext.ContentPanel('west-div', {fitToFrame:true}));

layout.addRegion('center', {margins: {top: 10, left: 5, right: 10, bottom: 10}}); layout.add('center', new Ext.ContentPanel('centre-div', {fitToFrame:true})); };

</script> </head> <body> <div id="west-div" style="background-color: orange;"></div> <div id="centre-div" style="background-color: green;"></div> </body> </html>

Not much to that at all. Here is what it looks like ...

Setting a {{layoutDecorator}} is completely optional. The following is also a valid page …

<html>
	<head>
		<meta name="layout" content="main" />
		<title>Non Decorated</title>
	</head>
	<body>
		Grails + Ext is cool!
	</body>
</html>
Which would look like this ...

Summary

This is just an example of one way that you could utilise Ext layouts and Grails layouts. Hopefully this example has given you some ideas on how you could utilise the power of Ext layouts within your Grails project.