Responsive Web Design with jQuery Grids: A closer look

Damyan Petev / Thursday, May 23, 2013

Responsive Web Design with jQuery GridsI really hope I got you excited about the Responsive Web Design mode for the Ignite UI Grids last time – as it is the kind of an unique feature that truly makes this jQuery control stand out! As previously explained it’s not exactly a simple thing to make a grid responsive, but our team made serious considerations on how to best approach this and I do believe they’ve pretty much nailed it and this will turn out to be very useful addition. There were also plenty of things that can’t fit in a single blog, plus the fact that the actual feature was not yet released so there wasn’t too much of demo material to see. So now, with 13.1 out and even accompanied by a service release, without further due, let’s dig into the technical stuff! I’ll start with some interesting design considerations and even potential pitfalls related with Responsive Web Design and then have some specific Grid tips.

A Getting Started, of sorts

Now these are more of honorary mentions as the most basic stuff were already covered elsewhere, but hey, it doesn’t hurt to have them on a speed dial. So in case you missed it - here’s a nice summary on Building responsive web design using CSS3 Media Queries!

And because creating a Responsive app is all in the name of providing users with a better experience I figured some UX tips and food for thought are in order. Our Services or as of recently D3 (from Discover – Design – Develop) have a solid expertise in the field and they share some of it in their blogs. For example this on UX of Responsive Web Design you should definitely check out! There’s a whole lot more on UX, Design and usability and I highly recommend keeping an eye on those.

Once you have technology and design considerations in check, I’d like to turn your attention to the now available Documentation for the igGrid Responsive Web Design (RWD) Mode. There’s an good coverage on the theory behind this and also a good amount of examples! Speaking of which, you can also check out our samples for Responsive Web Design Custom Mode Template Switching and Responsive Web Design Mode of the Grid with Twitter Bootstrap

Additional considerations

Some “good to know about” extras  – like the browser viewport. One of the most important things to consider is that many (if not all) smartphone browsers will 'lie' about their dimensions. It's not a bad thing, mind you – most phones these days have such high dpi that it will be impossible to read and interact normally with pages where everything is shrunk to the native resolution. Instead, as you can read on CSS pixel ratios (or, why all mobile sites are 320 pixels),  pixels we would use for web development are now relative. The term ‘CSS pixels’ also pops up here and there, but basically the browser will almost always scale the pixels down (so 1 pixel will actually be multiple physical ones), which in term affects the browser viewport dimensions. Those dimensions, usually wider than the screen, are still small enough to squish a non-responsive page and big enough to force the user to scroll. This is where the all-time favorite meta tag comes in setting the viewport to match the device width (which removes that annoying scrolling) but limits your space further, something in the range of 320 – 460 of them virtual pixels! Remember the utility class table from Bootstrap I showed last time? I hope it does it make sense now why phones end up in their respective visibility class even in landscape.

On the other side are tablets where many have a pixel ratio of 1 or 2 in the case of retina displays, but for all intents and purposes tablets usually hit the desktop size mark in landscape and only get to the tablet-specific styles in portrait. There’s a very large and very incomplete List of displays by pixel density on Wikipedia if you wan to have a look.

There’s also an interesting issue with setting the IE10 viewport in CSS (as is per recommendation) rather than using the meta tag. The issue came out of nowhere and this blog on Responsive Design in IE10 on Windows Phone 8 helped me a lot. As it turns out, the bug applies actual physical pixels when using “device-width” in CSS. Eventually I tracked down the issue to Bootstrap’s own CSS - they applied to fix Win 8 apps in snapped mode, but it completely broke Bootstrap pages on Windows Phone. Keep that in mind, there’s a fix provided that feels hacky at best, but it does work so consider it if working with Bootstrap.

Additionally, good things to remember is that touch devices have their special needs and oddities – make sure touch targets are big enough, remember touch events are separate and in the case of the Grid – consider applying the touch-friendly Windows UI Theme. And once more, have a look around those D3 blogs for mobile and usability related articles that can help you.

Merging and Column Hiding

As I hope it has become clear the Responsive feature allows for both hiding columns through setting and modifying the structure (markup) via templates. The template swapping is a source of quite a few possible modifications, perhaps one of the most useful ones is merging columns, as you may read in the docs. Now I know this might not be necessary, but better safe than sorry: don’t attempt to hide columns through the template or merge them (e.g. writing one less <td> cell). A main thing about the Ignite UI Grid is that is has columns array you usually define or have auto-generated – either way, row templates are designed to do just that – allow you to format the already existing layout! In other words, templated columns MUST match the layout defined or all data properties if generated! You will not get any results if column definitions and markup clash. So, for hiding columns you are stuck with defining column settings (not too pretty if they are a lot, I know).

So how then exactly do you merge columns? Well one option is hiding a column though settings and adding its data to another still visible ones in a template. This is the approach you can see in our samples and it works best when the target column header is something that makes sense after you merge, like in that sample the Address taking in the country and city values as well.

Another possible way that I personally like is though an Unbound Column. That way you can have the unbound setup with multiple data properties and only revealed when other are not. For example take this part of a grid definition where you have first and last name separate and an unbound column to take their place when  on phone:

  1. $("#grid").igGrid({
  2.     columns : [{
  3.         unbound : true,
  4.         key : 'name',
  5.         dataType : 'string',
  6.         headerText : 'Full Name'
  7.     }, {
  8.         key : 'FirstName',
  9.         dataType : 'string',
  10.         headerText : 'First name'
  11.     }, {
  12.         key : 'LastName',
  13.         dataType : 'string',
  14.         headerText : 'Last Name'
  15.     }],
  16.     //...
  17.     features : [{
  18.         name : 'Responsive',
  19.         forceResponsiveGridWidth : false,
  20.         columnSettings : [{
  21.             columnKey : 'name',
  22.             classes: "ui-visible-phone",
  23.             configuration: {
  24.                 phone: {
  25.                     template: "<span>${LastName}, ${FirstName}</span>"
  26.                 }
  27.             }
  28.         },{
  29.             columnKey : 'FirstName',
  30.             classes: "ui-hidden-phone"
  31.         },{
  32.             columnKey : 'LastName',
  33.             classes: "ui-hidden-phone"
  34.         }]
  35.     }]
  36.     //....
  37. });

Same thing using the ASP.NET MVC helpers:

  1. @(Html.Infragistics().Grid(Model)
  2.     .Columns(column =>
  3.     {
  4.         column.Unbound("name").DataType("string").HeaderText("Full Name").Template("${FirstName} ${LastName}");
  5.         column.For(x => x.FirstName).HeaderText("First name").DataType("string");
  6.         column.For(x => x.LastName).HeaderText("Last Name").DataType("string");
  7.         //...
  8.     })
  9.     .Features(feature =>
  10.     {
  11.         feature.Responsive().ForceResponsiveGridWidth(false).ColumnSettings(setting =>{
  12.             setting.ColumnSetting().ColumnKey("name").Classes("ui-visible-phone").Configuration(conf => conf.AddColumnModeConfiguration("phone", c => c.Template("<span>${LastName}, ${FirstName}</span>")));
  13.             setting.ColumnSetting().ColumnKey("FirstName").Classes("ui-hidden-phone");
  14.             setting.ColumnSetting().ColumnKey("LastName").Classes("ui-hidden-phone");
  15.             //...
  16.         });
  17.     }).DataBind().Render()
  18. )

Essentially turning merging the two columns going from tablet to phone mode in the images below:

jQuery Responsive Grid with separate columns in tablet mode

jQuery Responsive Grid with merged columns in phone mode

 

As you may remember and notice above, when Responsive is used with Column Hiding you get that hidden indicator that is both a cue for the user something else is there and an interaction target to modify the visible columns. In terms of usability for touch though, it does feel slightly hard to hit. Good thing you can pick its width I guess – it seems that something in the range of 14-15px is much easier to hit, which is about double the default size.

jQuery Responsive Grid in phone mode with Hiding indicators enlarged

Of course, unless you arrange your columns in a way that all the ones you will be hiding are in front or at the end, you will get multiple indicators like I have above. Increasing their size for touch takes up even more of the already precious space.

So either ensure arrangement or hide the indicator entirely - well, not really since right now there is no such option, but you can always set the width to 0! How would the user know of hidden columns and interact with the feature then, you may ask? Well, the Column Hiding and its API provide you with the necessary tools to build your own user experience by adding an external button to open the column chooser dialog and events to let you know and potentially display when columns are hidden to the user. The column chooser is easy to open with a single call and hiding and showing events are fired for each column separately (by both Hiding and Responsive!) so you can keep track of their count:

<button id="openChooser"> Show/Hide Columns </button>
<label id="hiddenColumns" data-count="0"> </label>
  1. $.ig.loader("igGrid.Responsive.Hiding", function () {
  2.     $("#grid").igGrid({
  3.         //...
  4.         features : [{
  5.             name : 'Responsive',
  6.             responsiveColumnHidden: hidden,
  7.             responsiveColumnShown: shown
  8.         },{
  9.             name : 'Hiding',
  10.             hiddenColumnIndicatorHeaderWidth: 0,
  11.             columnChooserWidth: 300,
  12.             columnHidden: hidden,
  13.             columnShown: shown
  14.         }]
  15.         //...
  16.     });
  17.  
  18.     //initial state after load
  19.     updateHiddenLabel($("#grid").data('igGrid'));
  20.  
  21.     $("#openChooser").igButton().click(function myfunction() {
  22.         $('#grid').igGridHiding('showColumnChooser');
  23.     });
  24. });
  25.  
  26. function hidden(evt, ui){
  27.     var label = $("#hiddenColumns");
  28.     var newHiddenCount = parseInt(label.data('count')) + 1;
  29.     label.data('count', newHiddenCount).text("(" + newHiddenCount + " hidden)");
  30. }
  31. function shown(evt, ui){
  32.     var label = $("#hiddenColumns");
  33.     var newHiddenCount = parseInt(label.data('count')) - 1;
  34.     label.data('count', newHiddenCount).text("(" + newHiddenCount + " hidden)");
  35.     if(newHiddenCount <= 0){
  36.         label.data('count', 0).text("");
  37.     }
  38. }
  39. function updateHiddenLabel(grid){
  40.     var hiddenCount = 0;
  41.     var columns = grid.options.columns;
  42.     for(index in columns){
  43.         hiddenCount += columns[index].hidden ? 1 : 0;
  44.     }
  45.     $("#hiddenColumns").data('count', hiddenCount).text( hiddenCount ? "(" + hiddenCount + " hidden)": "");
  46. }

Note that you should pick a fitting size for the chooser (300 looks ideal, remember that’s in CSS pixels) as the Draggable and Resizable widgets on the chooser are not likely to work too well, unless you use one of those widgets that add touch support to jQuery UI.

RWD grid in phone mode without the hidden indicatorThe column chooser for in the Responsive Phone mode

Of course, you can have the indicators and extra chooser shortcut as well – all depending on what fits your design needs better.

Multi-Column Headers

When you are using jQuery Grids with this feature there’s a slight consideration to keep in mind. With Column Hiding you can hide columns in groups directly through their multi-header keys.  Event though the Column Hiding feature itself  allows for such API calls  and we’ve also been using “hiding a column” through the Responsive feature, it’s not the same thing. Responsive, much like Hiding, is hooked to the underlying grid framework that just won’t accept such calls because they don’t exist there. So short version – Responsive can’t hide multi-columns at once through their common header, you will have to provide settings for each separately.

Responsive Hierarchical Grid

Noticing the title? ‘Grids’? Yes, plural. Decided I should show some love for those with more complicated(hierarchical) data on their hands. Sometimes by looking at what we write and demonstrate someone would think we are forgetting about the Hierarchical Grid. We aren't really, we are merely forgetting how easy it is for readers and users to forget that it is build on top of actual flat grids. You know, there's actually a very normal igGrid widget running alongside the igHierarchicalGrid every time, even for the parent layout. When we talk features and functionality, we almost always have both controls in mind and only mention special cases. Point is this particular Hierarchical Grid is not afraid of some Responsive Web Design!

The Responsive feature can be inherited but that, as with other features with column settings, won’t do you any good. In other words, the feature and its settings must be defined for each layout you want it on. Which is not half bad because you can apply separate recognizers and mode profiles for them.

Twitter Bootstrap is also supported, which is to say you can use Bootstrap CSS classes for column visibility and mode recognition. And it’s also super easy to set up. Introducing the Responsive Web Design page with Ignite UI jQuery Hierarchical Grid and Twitter Bootstrap… whoa, that’s a mouthful!

  1. $('#grid').igHierarchicalGrid({
  2.     dataSource: '/Home/Departments',
  3.     autoGenerateColumns: false,
  4.     autoGenerateLayouts: false,
  5.     responseDataKey: null,
  6.     columns: [
  7.         //...
  8.     ],
  9.     columnLayouts: [{               
  10.         columns: [
  11.             //...
  12.         ],
  13.         features: [{
  14.             name: 'Responsive',
  15.             responsiveModes: {
  16.                 phone: "bootstrap",
  17.                 tablet: "bootstrap",
  18.                 desktop: "bootstrap"
  19.             },
  20.             columnSettings: [{
  21.                 columnKey: 'BusinessEntityID',
  22.                 classes: "visible-desktop"
  23.             }
  24.             //...
  25.             ],
  26.             rowTemplate: {
  27.                 desktop: "<tr><td>{{html BusinessEntityID}}</td><td>{{html LoginID}}</td><td>{{html NationalIDNumber}}</td><td>{{html Gender}}</td><td>{{html BirthDate}}</td><td>{{html MaritalStatus}}</td><td>{{html JobTitle}}</td><td>{{html OrganizationLevel}}</td><td>{{html HireDate}}</td><td>{{html SalariedFlag}}</td><td><div class='progressbar'>{{html VacationHours}}</div></td></tr>"
  28.             }
  29.         }]
  30.     }],
  31.     features: [{
  32.         name: 'Responsive',
  33.         responsiveModes: {
  34.             phone: "bootstrap",
  35.             tablet: "bootstrap",
  36.             desktop: "bootstrap"
  37.         }
  38.     }]
  39. });

The only difference is that you have to specify the Bootstrap recognizer if you need it, and use unprefixed classes for the column visibility like “hidden-phone” and you should be good to go:

Responsive Web Design jQuery Hierarchical Grid and Twitter Bootstrap

And the template you notice above is just for desktop where you can enjoy power and space so why not have a column with Gauges or sliders or progress bars in my case:

Responsive Web Design jQuery Hierarchical Grid and Twitter Bootstrap

Thinking about it, you get responsive design mode grid with responsive design grids in it in a responsive web design site/app – this should totally qualify for an Inception meme!

Resources

I have some interesting tips and tricks coming up next, so stay tuned!

Designing for Responsive Design - Webinar

I’d love to hear some thoughts, so leave a comment down below or @DamyanPetev.

And as always, you can follow us on Twitter @Infragistics and stay in touch on Facebook, Google+ and LinkedIn!