Building Responsive Layouts in Liferay with Twitter Bootstrap

Twitter Bootstrap is a popular, easy-to-use front-end framework that not only allows for rapid prototyping, but also comes with a robust set of features that let you easily implement a responsive design.

If you’re building a site from scratch, all you have to do is include the Bootstrap CSS and JavaScript, and you’re off to the races! When you’re working with Liferay, though, it’s a bit more complicated than that. Of course, it is possible to create a new theme, drop the big bundle of Bootstrap files in there, and start running with it; but there are a lot of Bootstrap UI features that conflict messily with default Liferay styles, especially the Dockbar and Control Panel. We’ll handle workarounds for some of these in a future post; but for today, we’ll concentrate on the most powerful and useful feature of Bootstrap, responsive layouts.

For the sake of simplicity, we’re going to build a two-column layout with a main content column and a sidebar; but the possibilities are endless. Before we get stuck in, though, you’ll want to familiarise yourself with how the Bootstrap grid system works; and if you haven’t seen this excellent tutorial on how to build custom layout templates, it’s worth a quick read. I’ll draw your particular attention to the generated code at the bottom of this page, which will give you a better idea about what we’re going to be building from.

The Layout Template

Creating bootstrap-2-column-sample-layouttpl gives us a blank slate. Open up bootstrap_2_columns_sample.tpl, and let’s get to work.

We’ll start by duplicating the usual layout template scaffolding:

<div class="bootstrap-2-column-sample-layouttpl" id="main-content" 
    role="main">

<div class="portlet-layout">
    <div class="portlet-column portlet-column-first">
        $processor.processColumn("column-1", "portlet-column-content
            portlet-column-content-first")
    </div>
    <div class="portlet-column portlet-column-last">
        $processor.processColumn("column-2", "portlet-column-content
            portlet-column-content-last")
    </div>
</div><!--portlet-layout-->

</div>

The usual process from here is to let AUI handle the column widths by assigning CSS classes to them; say, for a 70/30 two-column split, assigning .aui-w70 to the first column and .aui-w30 to the second column. This is where we’re going to let Bootstrap do the heavy lifting.

We’re going to use Bootstrap’s fluid grid system to ensure that we have the best layout displayed regardless of how wide the viewport is. To make this happen, we’re going to use .container-fluid on our container [1] and .row-fluid on our row.

<div class="bootstrap-2-column-sample-layouttpl container-fluid" 
    id="main-content" role="main">

<div class="portlet-layout row-fluid">
    <div class="portlet-column portlet-column-first">
        $processor.processColumn("column-1", "portlet-column-content 
            portlet-column-content-first")
    </div>
    <div class="portlet-column portlet-column-last">
        $processor.processColumn("column-2", "portlet-column-content 
            portlet-column-content-last")
    </div>
</div><!--portlet-layout-->

</div>

Now to specify the widths of our columns, we’re going to use Bootstrap’s classes. Bootstrap layout is based on a twelve-column grid; so if we want our main content to take up two-thirds of the page and our sidebar to take up one-third of the page, our main content will be eight columns wide and our sidebar will be four columns wide, like so:

The Layout Template

Translating that into Bootstrap, that gives us .span8 and .span4, making the markup look like this:

<div class="bootstrap-2-column-sample-layouttpl container-fluid" 
    id="main-content" role="main">

<div class="portlet-layout row-fluid">
    <div class="portlet-column portlet-column-first span8">
        $processor.processColumn("column-1", "portlet-column-content 
            portlet-column-content-first")
    </div>
    <div class="portlet-column portlet-column-last span4">
        $processor.processColumn("column-2", "portlet-column-content 
            portlet-column-content-last")
    </div>
</div><!--portlet-layout-->

</div>

The Theme

Of course, adding all these classes to the layout template doesn’t do us any good unless we have something in the theme to support them. Remember how I said earlier that installing the entire Bootstrap bundle can cause ugly conflicts with default Liferay styles? Well, on the Bootstrap download page, there’s an option to just break out individual components. I’ve gone ahead and isolated just the Bootstrap layout elements, which you can download (along with the code for the sample layout template) here.

Instead of cutting and pasting all that goodness into your custom.css, you can just go ahead and call it at the top before all your custom rules, like this:

@import url(bootstrap.css);

/* All your custom CSS rules go here */

Thanks to Nate Cavanaugh for pointing out this more future-proof way of doing it! He explains further in the comments.

One more important change: even though we have a fluid grid, it will be helpful to set a max-width on the container-fluid just in case. [2]

The Finished Product

With the appropriate classes added to the layout template and the supporting CSS in the theme, you’re good to go! Deploy your theme and add some content. You’ll notice that it’s two columns at desktop width, but if you resize your browser, you’ll see the layout collapse to one column at 768px wide. If you don’t feel like dragging your broswer window around, here’s a handy bookmarklet you can use to view how your layout changes with viewport size.

Desktop view (1200px wide):

Desktop view

Mobile view (468px wide):

Mobile view

This is a very basic example that still needs some minor adjustments, but it shows how we can use popular and well-tested frameworks to extend Liferay’s capabilities. Using the basic .row-fluid and column classes, we can create complex layouts that work on all devices. Bootstrap offers even more features that make it trivial to target screen widths, but that’s a topic for another blog post.

Further Reading

Notes

  1. In practice, you’ll probably want to specify .container-fluid somewhere in portal-normal.vm, but we’re putting it in the layout template here just to keep everything self-contained. Return
  2. For the purposes of this demo, I made a couple minor changes to #wrapper in the Liferay Classic theme to make it play better with the layout template; but if you’re building a new theme and layout template from scratch, you’ll be better served to use .container-fluid in the theme, as I mentioned in Note 1. Return

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Post Comment