Liferay.com, mobile sites and responsive layouts

Bryan recently blogged about the new site design and how Liferay.com is displaying a mobile formatted website for different types of devices (go ahead and test it out, hop into any browser and resize the page as small as it can go). Today I want to discuss a bit how we achieved this from a technical level. This is one of the most common questions I not only got from people who attended our East Coast Symposium, but also inside of Liferay from other devs.

Liferay.com can resize from desktop to mobile dynamically

Because we like to keep our ear to the ground on all things front end, we’ve actually been following the discussion/debate about responsive web design since Ethan Marcotte published his article on the subject last year. If you haven’t read the article, the tl;dr version is that using CSS media queries, it’s possible to pass a different set of CSS styles to different devices and have a design that’s customized for a specific browser/device size.

As far as mobile strategies go, it’s probably the easiest to implement, and there are a lot of quick wins with it. However, Jon Neal pointed out to me when we were first discussing this idea that there’s very little difference between using CSS media queries and just using Javascript to simulate the same thing.

I’ve since read other people bring up a similar point, and it’s a fair one. In fact, there are multiple issues with CSS media queries that make them cumbersome. For instance, let’s say we want to share some styling across two specific devices. It’s possible using the logical operators but even then, you end up with duplicate CSS (and it becomes much harder to read). And the fact that IE doesn’t support media queries was another major impediment to us using it, so we were going to need to use JavaScript anyways (either to add the CSS classnames or to parse mediaqueries).

So Jon wrote up a script that would handle this logic for us. Here’s how the script works: When the page is loaded or resized, the script checks the width of the window and it looks into an object of 4 major size specifications; 960, 720, 480, and 320.

What do those numbers mean? 960px is based on the uber-popular 960 grids which is a layout width that looks great on most desktop computers, and the iPad in landscape mode. 720px is a layout width that looks great when the iPad is in portrait mode. 480px is the width of the window of most smart phones in landscape mode. 320px is the width of the window of most smart phones in portrait mode.

The other great thing Jon came up with was to add greater-than and less-than css classes. I’ll show how this works in a second, but the idea is basically, you can not only target a specific device width, but also if it’s greater than or less than any of those above widths.

If your target device is 600x800, you can still target that device with the css classes.

With what Jon prototyped, I’ve created an Alloy module that codifies this so that it’s super easy to use (while at the same time, staying out of the way for users who don’t wish to use it).

For a quick and simple demo, open up the viewport demo (if you view it in Chrome or Safari, Firefox or Opera, you’ll see some cool CSS3 generated content/animation, but the demo works in any browser).

To get the module to run, all you need to do is call: AUI().use('aui-viewport') in a script tag anywhere on your page.

Now you can target your site design to specific device sizes. Let’s say we want to give our navigation items to sit side by side when we view on the ipad or larger, but that we want to have them stack on smart phones:

#navigation li {
    display: inline;
    float: left;
}
.aui-view-lt720 #navigation li {
    display: block;
    float: none;
}

or let’s say we don’t want to make sure our images don’t exceed a certain width on the iPad in portrait mode

.aui-view-720 img {
    max-width: 300px
}

Or perhaps we want to target just portrait mode of smartphones and tablets:

.aui-view-720 body, .aui-view-320 body {
    background: #ccc;
}

You can even combine this with our other browser selectors to target very specific browsers:

.touch.aui-view-lt720 {} /* Touch based smartphones */
.webkit.aui-view-lt960 {} /* Webkit based tablets and smartphones */
.win.aui-view-720 {} /* Smaller browser views on just Windows */

Now, how is the logic applied? If the screen is equal to or between any of our defined widths, it will get this CSS class: aui-view-{WIDTH}. And, if the screen is greater than any of our widths, it will also get: aui-view-gt{WIDTH}. Lastly, if the screen is less than any of our widths, it will also get: aui-view-lt{WIDTH}.

So a window size of 1900x1200 would get:

aui-view-gt320
aui-view-gt480
aui-view-gt720
aui-view-gt960
aui-view-960

whereas a window size of 800x600 would get:

aui-view-gt320
aui-view-gt480
aui-view-gt720
aui-view-720
aui-view-lt960

Notice that the classes represent the width rounded down to the nearest division. This is to guarantee that your styled layout won’t exceed the current width. Because of that the css class doesn’t exactly represent the current width, but rather enforces you to think about the layout at specified sizes and devices.

Caveats

Life is not all cheese and bananas with this system, as there are some caveats to be aware of:

  • You are delivering the same content to all devices.

    Depending on the project, this could either seal the deal or break it. There’s an added cost to shipping down different images for the same page, and some mobile websites may wish to send only a very limited set of content to mobile devices (or perhaps very targeted content). The answer to this then is decide which of the 2 strategies you wish to use for your specific project: same content for all devices, but dimension specific designs, or redirect to a mobile/touch specific community that serves a targeted set of content and design.

  • Designs can require more planning for each of the design layouts

    You’ll notice that the design now has to accommodate basically 4 different views: desktop, tablet(portrait), and smartphone (portrait and landscape). This of course means thinking about a different set of design compromises and layouts for how the same content should fit (or which content should be hidden) for different types of views. Of course, this caveat extends to both content strategies, but having the same content automatically in every view often requires a bit more forethought during the design process.

How can you get this module?

If you’re an Enterprise Edition customer, this module will be available in the 6.0 SP2 service pack (and the great part is, if you don’t want to use it, it will have 0 affect on your application). If you’re running Community Edition, this will be in Liferay 6.1 or if you’re running trunk of Liferay you can see it today. We’ll also be releasing another version of AlloyUI soon that will have it included.

Conclusions

Overall, the response has been great, and the design/development process was surprisingly smooth. I would recommend it as a general principle for your design, as it helps our designs fit the fluid nature of the web.

More information

Responsive Web Design by Ethan Marcotte
MediaQueri.es
The practicalities of CSS Media Queries, lessons learned.

Blogs
Thanks nate, nice article.
Is there any YUI plugin availale for this? I want to use it in simple htmls.

Thanks,
Wasim Shaikh,
http://microblog.wasimshaikh.com
@Szymon:
In this case, it can work with any of them. Basically, it just depends on what portlets you'll style in which view. For some mobile devices, some portlets might make less sense (for instance, a flash portlet wouldn't work so well on most smart phones these days). But since its just CSS, the possibilities are pretty wide emoticon

@wasim:
Not yet, but I'll be pushing this one over to the YUI Gallery, so as soon as it's up, I can let you know emoticon

Thanks all!
This is a great feature and I got it to work nicely on my liferay except that it isn't getting picked up on the iphone (haven't tried it on an ipad yet). I noticed the viewport demo doesn't work correctly on the iphone either (it's being treated as desktop size 960px) - do you know why this may be?
Dear Nate,
<br>
I want to use my own JavaScript calendar and date picker instead of aui-calendar and aui-datepicker all over Liferay. I want to replace my js files with those of aui, but clearly this will not work; what functions should my files have and what should they return?
<br>
Thanks in advance
Nate,

Thanks very much for this; however, I must not be doing something quite right as it doesn't seem to have any effect on our pages.

Where would I put the call to AUI().use('aui-viewport')? I've tried doing it in the script section of the page (Manage>Page>Script), but that does not result in generating the expected classes in the HTML. We are EE customers and I can see that AUI().use... is inserted in the source of the HTML, but that's it.

Can you provide a pointer to further information?

Many thanks and sorry for the very basic question!
Hi Ben,
Thanks for reaching out. The only thing I can think of that would cause this not to work is perhaps the version of EE you have isn't the latest service pack? This component was included in a service pack, so it's not available in earlier ones.
If you get in touch with your account rep, they can probably get you a patch that would include just the files you need for this feature.

If you are on the latest service pack and it's still no go, you can either get in touch with support to help debug your specific case, or you can create a forum thread and paste the link here and I'll take a look at it and see if I can spot anything.

Thanks Ben,
Has this been carried over to 6.1? I do not see anything in CE 6.1 GA1. If not can someone write a log about the mobile device rules and what they are how to use them and examples?
Would prefer the view-port was there so we don't have to do much with more guidance on how to use in custom themes
Nate this is really great stuff. Have you run into the following: works great on the desktop monitor (able to minimize browser window and the alternate css is used), but fails on an actual handheld device... ( iPhone, android, etc....)
Hi Steve,
I have run into an issue on Retina screens where the reported size wasn't being properly handled.
In those cases, I had to set something like the following in the head of the document:
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0;" />

Let me know if that helps.

Thanks,
And Dave, yes this module is inside of 6.1 as well. As long as you call AUI().use('aui-viewport') in your theme, you should be good to go.

HTH,
Nate, Thanks for this tip! My initial tests seem to work perfectly! Now on to redesigning for these devices/smaller screens! Well Done! This certainly has a far reaching impact!

Steve

<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0;" />
[...] Here is exactly what you need: http://www.liferay.com/web/nathan.cavanaugh/blog/-/blogs/liferay-com-mobile-sites-and-responsive-layouts Flag Please sign in to flag this as inappropriate. Mark as an... [...] Read More
Hi ,

We have been trying to build a mobile theme in Liferay using repsonsive web design.
We have included the line : AUI().use('aui-viewport'); within script tags and also included : <meta name="viewport" content="target-densitydpi=160dpi, width=device-width, minimum-scale=1, maximum-scale=1"> within the <head> tags of portal_normal.vm
The theme is working fine on Android , Nokia ,Apple mobiles but gives issues on Blackberry (OS 4).We have tested it on firefox browser for BB 8520 and the problem is that the web site rendered as a full site on Blackberry Firefox and does not use responsive layout.The client's major requirement is that the mobile theme should open up on Blackberry.

Any suggestions in this regard will be highly welcome.Need urgent help on this.


Regards,
Md. Mohiuddin
Hi, sorry for bothering but i don't know where to put the AUI().use('aui-viewport') declaration (i have already tried to put in a script inside a custom portlet but nothing happens). I just want my portal to adjust it size to the browser window. I have the liferay-portal-6.1.0-ce-ga1 version. Please can somebody help me?
Hi Rui,
If you place it in your theme's main.js it should work.

However, as mentioned above, what this does is add CSS classes to the HTML element. For the styling to actually change, you'll need to add CSS for those sizes to determine how it should look.

I hope that helps emoticon
Sorry to bother again Nate but in the forum, they told me that the classic theme from the liferay portal has already this feature. I have the liferay portal CE 6.1.0 version but in my case that's not true. Is there any theme with this feature that i could download? Many thanks
I have found a BIG problem with responsive design with OOTB Liferay 6.1 GA1 not sure if in GA2.

the WYSIWYG editor puts full style conditions for images - it sets height and width styles (generally a no no anyway). As these are at the element level they cannot be overridden by say aui-view-lt720 img {max-width:50%"} definitions in a css file.

How can we get the WYSWYG to behave in a responsive friendly manner, otherwise we have to edit the source of all our content!
Hi Dave,
This is something on CKEditor's side. I'm not sure if there are any out there that are responsive friendly per-se, but if you ever find one, Liferay allows you to swap out the editor.

However, there are a couple of different ways to handle this without changing the editor:
1. In your CSS, setting max-width actually should be respected, because the inline style is being set to width, not to max-width. When I test that scenario, max-width works fine.
However, the issue with it is that the height is still set, so it will squish the image.
What you can do is:
.aui-view-lt720 img { height: auto !important; max-width: 50%; }

This should make it so that the inline style is overwritten, but the image proportions are still maintained.

2. The other option you have is to discourage or not allow images to be embedded in the editor, and instead, add a field for the image into the structure.
This is what we a lot for the Liferay.com website. Usually, there are blocks where we want a paragraph of text and some accompanying image or any number of those. We'll create a repeatable structure, and allow those to be uploaded.
In the template then we have more control over how that image is displayed with the text for the different breakpoints.

The easiest of these approaches is probably #1 as it requires the least amount of change, but #2 provides you with ultimate control.

I hope that helps emoticon
This is a great feature.
Could we get sample theme war file or in market place? its will help us.

Thank's
where say for two levels of nav, I envision

for browser: show nav as usual (on mouseover of navItemLevel1 show navItemLevel2s)

for tablet portrait and less: show only navItemLevel1s where onclick/touch of navitem; go to targetted page where then only show navItemLevel2s (under the navItemLevel1 u just went to)

possible ? do themes need to be jsp for this or can this be done with velocity ?
Hi Nate, is it possible to create a responsive theme in Liferay 6.0.5 CE?
Yes. It's possible Juan.
You need to overwrite some default Liferay CSS and all will work fine with @media query.
Thanks Kushal, could you give me an example or article on how to implement it?, Please.
Sorry I cannot let you know the site name.
But I can share CSS.

CSS:
-------------------------------------------------
/* common behaviour */
.portlet-layout { display: table; width: 100%; }
.portlet-column-first { padding-left: 0 !important; }
.portlet-column-last { padding-right: 0 !important; }
.portlet-column { padding: 0 1%; display: table-cell; vertical-align: top; }

/* tablet behaviour */
@media all and (min-width: 768px) and (max-width: 1024px) {
.portlet-column { padding: 0 3% 0 0; display: table-cell; }
.home .portlet-column { padding: 0 0 0 0; }
}

/* tablet behaviour - viewport change */
@media all and (max-width: 767px) {
.portlet-layout { display: block; }
.portlet-column { display: block !important; padding: 0 !important; }
}
/* smartphone behaviour */
@media all and (max-width : 480px) {
.portlet-layout { display: block; }
.portlet-column { display: block; padding: 0 !important; }
}