jQuery Responsive Grids: Tips and Tricks

Damyan Petev / Tuesday, June 25, 2013

Responsive jQuery Grids: Tips & tricksSo far we’ve had an overview of the Responsive Web Design mode support for the Ignite UI Grids and some considerations with general implementation, merging columns, combining with column hiding and with hierarchical Grid. By now you should have the basics, but I also promised some tips and tricks and I plan to deliver. For example, how would you go about Updating/Editing experience on a mobile device? Where could custom mode recognizers be truly valuable? What happens with a Hierarchical Grid and its children set up with Responsive when it switches mode? Well, read on to find out obviously!

Mobile editing with the Updating feature

So here’s a little twist I came up with when considering the editing experience. If you have checked out the demos you may know how the basic row / cell editing goes – the user does the changes inline in the grid and optionally hits ‘Done’ to save. However, considering you are hiding columns on smaller screens, how do you provide the full editing experience? I mean, do you force the user to swap columns to perform edits? Moreover, do you force to visible everything in order to add a new record with mostly required fields? Is that even feasible on a phone for example? I think not. So the only logical solution is to go the way many interactions are transformed for mobile use – dialogs, pages, navigation. I hope you already know where I’m going with this – yup the Row Edit Template! Once you have that set up you might notice something is not quite as you expected – the edit template is designed to deliver identical functionality in the dialog as the inline version, meaning it actually skips the hidden fields in process as well. Fortunately, this behavior is something that can be easily modified – it has been brought up to our attention before in this forum post and the solution can be easily modified for the needs of using row editing dialog in Responsive mode! Since in JavaScript there are many different values that evaluate as false, we can even deal with multiple hidden columns without needing to keep tack of column indexes to modify state back and forth, making the following snippet pretty reusable I hope:

  1. {
  2.     name: "Updating",
  3.     rowEditDialogOpening: function(event, ui) {
  4.         var columns = ui.owner.grid.options.columns;
  5.         for (i = 0; i < columns.length; ++i) {
  6.             //use 0 instead of false to be able to differentiate when restoring state
  7.             if (columns[i].hidden) columns[i].hidden = 0;
  8.         }
  9.     },
  10.     rowEditDialogOpened: function(event, ui) {
  11.         var columns = ui.owner.grid.options.columns;
  12.         for (i = 0; i < columns.length; ++i) {
  13.             if (columns[i].hidden === 0) columns[i].hidden = true;
  14.         }
  15.     }
  16. }
  17. //..

Now there’s one more thing to consider really – the sizing of the dialog itself. We want less scrolling and better usage of the space available and the default width and height options of the dialog don’t exactly fit every situation. What you can do is expand the rowEditDialogOpening handler to also adjust the size of the dialog based on the window width and to avoid scrolling – the grid’s height. And since desktop users can easily resize it, I chose to make it for tablet and phone only to demonstrate you can also make this conditional:

  1. //...
  2. rowEditDialogOpening: function(event, ui) {
  3.     if ($(ui.owner.element).igGridResponsive("getCurrentResponsiveMode") != "desktop") {
  4.         ui.owner.options.rowEditDialogWidth = document.body.clientWidth * 0.9;
  5.         ui.owner.options.rowEditDialogHeight = ui.owner.grid.element.height();
  6.         ui.dialogElement.children('.ui-dialog-content').css('height',ui.owner.grid.element.height() - 115);
  7.     }
  8.     var columns = ui.owner.grid.options.columns;
  9.     for (i = 0; i < columns.length; ++i) {
  10.         //use 0 instead of false to be able to differentiate when restoring state
  11.         if (columns[i].hidden) columns[i].hidden = 0;
  12.     }
  13. },
  14. //...

So what you end up with is a complete editing experience that fits mobile and desktop alike:

Responsive  jQuery Grid in Phone mode with Row Edit template showing all columns for edit.

When the window stood still..

..but the page content did not! So what happens when you need a Grid in some dynamic layout page? What I mean by this is a page where elements are resizable and/or can be moved around (usually by the user, but not necessary). So imagine a case with columns the user can resize or tiles from a tile manager or even the contents of a resizable dialog or split screen scenario – what happens when your grid needs to be inside one of those?  What happens when the user squishes the container, but the window and CSS media rules say nothing changed? Well as you can imagine, nothing, however the reason I bring this up is because there’s a good solution at your disposal – custom mode profiles! As you can see in our documentation you can define custom Responsive profiles with your own width settings and other settings. But outside of custom sizes (you can also set by overriding the CSS rules) what would make custom logic shine? Well, defining the mode parameter (use CSS based on media queries for orientation or dpi for example) or the element against which the measurements are taken (like the grid itself rather than the window). That’s exactly what we want and here’s a little extra trick – you might've noticed that you can create either inline to pass as options or extend a class in our namespace so you can use a custom “isActive” method. And there’s a third way – initialize the profile inline yourself so you can also pass options and have a reusable profile:

  1. $.ig.loader("igGrid.Responsive.Hiding.Paging,igDialog", function () {
  2.     $.ig.DynamicMode = $.ig.DynamicMode || $.ig.ResponsiveMode.extend({
  3.         settings: {
  4.             //default value if none provided
  5.             id: ""
  6.         },
  7.         init: function (options) {
  8.             this.settings = $.extend(this.settings, options);
  9.             this._element = $("#" + this.settings.id);
  10.             this._super(this.settings);
  11.             return this;
  12.         },
  13.         isActive: function () {
  14.             if(!this._element.length){
  15.                 //use default implementation if no element found
  16.                 return this._super();
  17.             }
  18.             // parent(), as the grid element might not have width initially if table
  19.             if (this._element.parent().innerWidth() < this.settings.maxWidth && this._element.parent().innerWidth() > this.settings.minWidth) {
  20.                 return true;
  21.             }
  22.             return false;
  23.         }
  24.     });
  25.  
  26.     //USAGE:
  27.  
  28.     $("#grid").igGrid({
  29.         //...
  30.         features : [{
  31.             name : 'Responsive',
  32.             forceResponsiveGridWidth : false,
  33.             responsiveModes : {
  34.                 phone: new $.ig.DynamicMode({ id: "grdid", maxWidth: 767 }),
  35.                 tablet:  new $.ig.DynamicMode({ id: "grid", minWidth: 768, maxWidth: 979 }),
  36.                 desktop:  new $.ig.DynamicMode({ id: "grid", minWidth: 979, maxWidth: Number.MAX_VALUE })
  37.             },
  38.             columnSettings : [
  39.             {
  40.                 columnKey : 'BusinessEntityID',
  41.                 configuration: {
  42.                     phone: {
  43.                         hidden: true
  44.                     }
  45.                 }
  46.             }
  47.             //...
  48.             ]
  49.         }]
  50.     });

And for example in a dialog without actually changing device or orientation, you can have a responsive-like behavior in a dialog window for example:

 Ignite UI jQuery Grid: Responsive feature inside a resizable dialog window

Note that you can’t do the same type of profile initialization while using the ASP.NET MVC wrappers and you will have to extend a profile with settings for each mode and assign it as predefined mode as usual.

Hierarchical Grid

Since it’s all about experience, with the Hierarchical Grid the Responsive feature is very different from the way Paging feels for example – when going to a different page you expect the layout to change and even if you do go back, the state of expanded rows being preserved, while nice, is not something expected or demanded. However in the simple act of switching from landscape to portrait browsing or vise versa, the average user will probably be expecting everything to stay mostly the same. It’s just layout, right? As soon as the parent layout starts changing with modes the rebinding and rendering of the Grid will happen and state of child layouts will be lost along the way. This is very likely to happen with tablets – as I mentioned before they hit the usual desktop sizes in landscape orientation or in the resizable container as above. If you can define a small amount of columns for the parent so it fits everywhere it’s good, but you can also save state and restore it as you see fit. As always the rich API the rescue.

Start by handling the “responsiveModeChanged” – what’s so neat about it is that it fires way before anything else happens, literary as soon as the feature detects a change and still hasn’t re-rendered the grid or modified layout settings. This is the ideal point for storing state. The next event is of the grid itself, the “rowsRendered” which fires every time the control is done rendering and is perfect to restore state. Here’s a snippet:

  1. var expanded = [];
  2. $.ig.loader(function () {
  3.     $('#grid').igHierarchicalGrid({
  4.         //...
  5.         features: [{
  6.             name: 'Responsive',
  7.             columnSettings: [{
  8.                 columnKey: 'DepartmentID',
  9.                 classes: "ui-hidden-phone"
  10.             }, {
  11.                 columnKey: 'ModifiedDate',
  12.                 classes: "ui-hidden-phone"
  13.             }],
  14.             responsiveModeChanged: function (evt, ui) {
  15.                 expanded = [];
  16.                 var HGrid = ui.owner.element.data("igHierarchicalGrid");
  17.                 ui.owner.grid.allRows().each(function (index, row) {
  18.                     if (HGrid.expanded(row)) {
  19.                         expanded.push($(row).data("row-idx"));
  20.                     }
  21.                 });
  22.             }
  23.         }],
  24.         rowsRendered: function (evt, ui) {
  25.             if ((len = expanded.length) > 0) {
  26.                 for (i = 0; i < len; i++) {
  27.                     ui.owner.element.data("igHierarchicalGrid").expand(ui.owner.rowAt(expanded.pop()));
  28.                 }
  29.             }
  30.         }
  31.     });
  32. });

The Hierarchical widget offers a way to check if a row is expanded and programmatically expand rows, while the actual rows are in the root flat Grid. It really is that simple.

Responsive Hierarchical Grid retaining the expanded row between different modes.

Do take a note that expanding the child Grids (especially if there are plenty of them) might not always be the best idea – it can create absurdly long girds to scroll through and while the grid creating is very fast, mobile browsers can still slow down if you don’t disable the expand animation or expand too many grids. I’m merely throwing ideas out there so always consider your own goals and designs!

P.S. Sorry for the slightly large GIFs - I saw no way to decently describe the effects of the tips above without motion :/

Resources

Check out the demos and other useful links below:

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!