jQuery Grid Checkbox Column alternatives

Damyan Petev / Monday, August 13, 2012

The checkbox column (introduced with the last release, see this post on Using Checkbox Columns with the Infragistics jQuery Grid) was created to provide a ‘shortcut’ for transforming columns with Boolean values into checkboxes and mostly became reality due to the great interest in such functionality from customers (and after all it does make perfect sense).

jQuery Grid Checkbox column

Of course, this isn’t a perfect world and creating defaults that can fit everyone’s needs is probably a lost cause. If you take a look at the introductory blog above, it is indeed saving tons of time – setting a single property is all it takes. That’s good and all, but as it can even be seen in the comments there and on our forums ( this post for example) there is a limitation – the checkbox column works just great, but it goes all shy when some templating is involved.

The problem

In the Grid API reference under ‘renderCheckboxes’ it says “That option is not available when jQueryTemplating is used.” That’s just a slight bit misleading and is most certainly going to get updated, since this does, in fact, include our own Templating Engine. So in essence the above should be read as ‘That option is not available when Templating is used’. That is because, by design, the Checkbox column feature is suppressed in such cases, as a row template may have its own rendering for the column.

That may or may not be an intuitive explanation as to why it will not be available with column template as well, but once you think about it – the grid renders rows, it has a default template to do that – a row template. You defining a column simply ends up being included somewhere along the way in that row template, instead of the default. That is why, templating in general will disable this feature.

But not to worry, there are options as always. I stumbled upon this and since customer feedback pointed this out for us (Keep it up, we like constructive feedback!) in this blog I’ll share my experience with coming up for alternative ways  to have such functionality.

A solution

Template Checkbox

Odd enough the problem is also main part of the solutions as it is the easiest way to alter the UI representation of your data and is also very flexible to be tweaked to your liking. Using this to add checkboxes in not all new (Forums » NetAdvantage for jQuery » igGrid » Infragistics IGGrid with checkbox) and as mentioned the feature itself is meant to be a shortcut for that.

A fair warning:  I'm sharing my experience and because of that I’ll go through two different approaches I took. Before diving in using one, make sure you have seen and considered both.

So the first approach is to use a plain HTML input of type checkbox tag and assign it our value. This is the approach you can see in both forum posts linked above as well. You can apply the template to your row template (if you are already using one) or simple assign it to a single column. For demonstration purposes I’ll use the Employees table from AdventureWorks (AdventureWorks Sample Databases (MSDN)) for the Salaried flag column and here is how a grid configuration would look like:

  1. $("#grid").igGrid({
  2.     primaryKey: "BusinessEntityID",
  3.     height: 550,
  4.     dataSource: "@Url.Action("Employees")",
  5.     autoGenerateColumns: false,
  6.     columns: [
  7.         { key: "BusinessEntityID", width: "50px", headerText: "ID", dataType: "number" , template: "<a style=\'font-size:20px;\' href=\'http://msdn.microsoft.com/en-us/library/ms124432(v=sql.100).aspx\'>${BusinessEntityID}</a>'},
  8.         { key: "LoginID", width: "250px", headerText: "Login ID", dataType: "string" },
  9.         { key: "JobTitle", width: "220px" , headerText: "Job Title", dataType: "string" },
  10.         { key: "SalariedFlag", width: "120px", headerText: "SalariedFlag", dataType: "bool", template: "<input type=\"checkbox\" {{if ${SalariedFlag} === \"true\"}} checked=\"checked\" {{/if}} disabled=\"disabled\" style=\'display: block; margin-left: auto; margin-right: auto;\'>'}
  11.     ],
  12.     features: [
  13.         { name: "Filtering", mode: "advanced", type: "local" },
  14.         { name: "Sorting", type: "local" }]
  15. });

Do note that the first column also has a template – a link to the very same table’s description on MSDN – and, as it is visible, that does not stop the checkboxes from appearing in addition to working just fine with features such as sorting and filtering:

A basic alternative to the checkbox column using template and simple HTML input type checkbox.

Enhancements

The above template is but a mere rendering tweak and doesn’t come with an interaction package. One other fair point made by customers is that the default interaction can be improved as Checkbox column requires 3 clicks to update – which is how row editing with selection behaves – 1 click to select, another to enter edit and third to change the value. Event though this wasn’t the original issue, since you are now in control of the checkboxes, why not improve things a bit?

The first step is to remove the disabled part, of course. Then you would need to handle user interaction with the checkbox – so add something along the lines of:

  1. data-rowid=\"${BusinessEntityID}\" onchange=\"checkboxChanged(event);\"

to the template. For enabled checkbox inputs onchange and click events are basically interchangeable, so pick one, but the name of the parameter passed to the handle *must* be ‘event’. The ‘data-‘ attribute is the easiest way to store information for the checkbox location – the grid does the same thing with rows elements if you take a closer look and it’s much easier to store it at creation as you then have access to the separate row data entry and will hardly add any overhead. Now just how easy it is with the Updating API to put that template to use:

  1. function checkboxChanged(evt) {
  2.     // get rowID where change occured:
  3.     var rowId = $(evt.currentTarget).data().rowid;
  4.     $("#grid").igGridUpdating("updateRow", rowId, { "SalariedFlag": evt.currentTarget.checked });
  5. }

Using the jQuery data() method gives us that row Id in a snap and the passed event can tell us the state of the checkbox to assign to that column value. Do note that this requires Updating to be loaded as a resource and added to the grid features(NetAdvantage® jQuery Online Help : igGrid Updating). And that leads to some issues that may arise

When Updating enters the scene..

It’s fairly reasonable to assume, since editing the checkboxes and Updating are already involved, that the grid is supposed to provide the full editing experience. However, once edit mode activated the edited cell/row UI is separate one, which means that if you don’t implement a provider yourself you will get the default combo-style editor:

What happens with the template in edit mode.

Now that may or may not be a problem for you (it will work just fine and checkboxes will update once you exit edit mode), but the sake of consistent experience it would be nice if the editor could be a checkbox as well..preferably the same one. At this point you have quite a few options, really, I’ll just provide a few examples how I think this can be made better and usable.

One little trick to try is setting that very same column as read-only. You already have a control that sets the value of the cell regardless of edit state and therefore you can go ahead and set that very same column as read only! What that would do is prevent the cell from getting its UI replaced by editor and in row mode it will remain unchanged. That means no combo like above and the very same checkbox with the same event handler still there ready for action. One tweak required though – since the Update Row method is not meant to be called from within edit session it will update the UI. All good? Nope. The issue is that in the case where you have the grid with the above setup and attempt updating a row when editing is activated – then awkward behavior is guaranteed. The UI is updated, but the editing is still active even though the grid looks like it exited that one and since there are not buttons or other elements that handle the editing of the editing session.. well it’s stuck. I see a few options here, things like ending the editing (method is available of course) before calling the update row, but that immediately exits on checkbox change and that is probably not what the user would be expecting to happen. Then again you can disable the checkboxes while editing, but that’s not the way to go when editing is active.

What you can do is save the change directly to the data source transaction logs, after all the checkbox will take care of its UI, but it might require additional work (data dirty event, rebinding) to work properly with other features. You can also simply restart the editing on the very same row for the user:

  1. function checkboxChanged(evt) {
  2.     // get rowID where change occured:
  3.     var rowId = $(evt.currentTarget).data().rowid;
  4.     // if editing is active - update just the cell, otherwise use row update
  5.     if($("#grid").igGridUpdating("isEditing")){
  6.         $("#grid").igGridUpdating("endEdit");
  7.         $("#grid").igGridUpdating("setCellValue", rowId, "SalariedFlag", evt.currentTarget.checked);
  8.          
  9.         //optionally can start the editing on the same row again
  10.         //need row index:
  11.         var rows = $("#grid").igGrid("rows");
  12.         var rowIndex;
  13.         //var context = this;
  14.         $.each(rows, function (index, row) {
  15.             if ($(row).data().id == rowId) {
  16.                 rowIndex = index;
  17.             }
  18.         });
  19.         //make sure you let the calling code exit before attempting start edit,
  20.         // a few milliseconds should be enough
  21.         setTimeout(function () { $("#grid").igGridUpdating("startEdit", rowIndex, 1); }, 2);
  22.     }
  23.     else {
  24.         $("#grid").igGridUpdating("updateRow", rowId, { "SalariedFlag": evt.currentTarget.checked });
  25.     }
  26. }

Above snippet shows getting the row index from the ID and starting editing on that row with a small delay (event handler is called within anonymous function elsewhere). Ending and starting editing causes the change of UI, which in turn causes a slight flicker, but with some desire and CSS tweaking that can be changed.

Do note that you can explore the possibility of creating custom editor provider for even better experience and I will show some about that in the next part.

No Boolean? No problem!

If you noticed so far there’s a really weak connection between the actual value and the representation – our logic dictates both representation and interaction result. SO nothing is really stopping you to use more than just Boolean – strings, numbers, null values – all quite plausible. Perhaps explore more than two states of the checkbox as well. Let’s have AdventureWorks’ SpecialOffers table where a MaxQty property for each offer states a maximum OR ‘NULL’. I want to not just display that data, I want to display an result of assessment of that instead. Might not be the best example, but I will have a column that will instead show if the offer has a maximum at all:

  1. $.ig.loader(function () {
  2.     $("#grid").igGrid({
  3.         primaryKey: "SpecialOfferID",
  4.         height: 550,
  5.         dataSource: "@Url.Action("SpecialOffersData")",
  6.         autoGenerateColumns: false,
  7.         columns: [
  8.             { key: "SpecialOfferID", width: "100px", headerText: "ID", dataType: "number" , template: "<a style=\'font-size:20px;\' href=\'http://msdn.microsoft.com/en-us/library/ms124455(v=sql.100)\'>${SpecialOfferID}</a>'},
  9.             { key: "Description", width: "250px", headerText: "Description", dataType: "string" },
  10.             { key: "Type", width: "220px" , headerText: "Type", dataType: "string" },
  11.             { key: "MaxQty", width: "150px", headerText: "Has MaxQty", dataType: "string", template: "<input type=\"checkbox\" {{if parseInt(${MaxQty}) }} checked=\"checked\" {{/if}} data-rowid=\"${SpecialOfferID}\"  data-test=\"${MaxQty}\" onchange=\"checkboxChanged(event);\" style=\'display: block; margin-left: auto; margin-right: auto;\'>'}
  12.         ],
  13.         features: [
  14.             { name: "Filtering", mode: "advanced", type: "local" },
  15.             { name: "Sorting", type: "local" },
  16.             { name: "Updating"}]
  17.     });
  18. });

Note the checkbox is being checked only if the value is not one of the ‘false’ equivalents (parsing returns NaN for example when it fails). Also you can set the values in much the same way – add null for  false and default/calculated value for true:

  1. function checkboxChanged(evt) {
  2.     // get rowID where change occured:
  3.     var rowId = $(evt.currentTarget).data().rowid;
  4.     var newValue = evt.currentTarget.checked ? 10 : null;
  5.     $("#grid").igGridUpdating("updateRow", rowId, { "MaxQty": newValue });
  6. }

The results (both from SQL Management Studio and the running app with the jQuery Grid):

It's possible with checkbox template to display easily non-boolean values.

And again the mentioned Updating tricks can be implemented for this in an identical manner.

Moving on

Here’s a quick comparison between the DIY template vs. the default Checkbox column:

PROS

  • Will work with other templates.
  • Easy to implement - 1 line for simple scenarios.
  • Not Limited to just Boolean values.
  • As a custom solution it is not limited to a bi-state checkbox, an tri-state checkbox can be potentially implemented(JSFiddle showing a simple 3-state checkbox implementation.)

CONS

  • Does require some code to achieve similar results.
  • Can be somewhat complicated to implement IF it is also required to function within the Updating edit mode. Overall in tricky relationships with the Updating feature.
  • The browser implementations don’t look anything special and (correct me if I’m wrong) are not guaranteed to look the same either.

Hopefully this experience was helpful for all of those that have greater functionalities in mind in that defaults can provide. As I said above this is just me sharing experience, there’s a learning curve here and this is not the only or final solution. Stay tuned for another approach in upcoming blog!

Demo project is available for download. As always, you can follow us on Twitter @DamyanPetev and @Infragistics and stay in touch on Facebook, Google+ and LinkedIn!