jQuery Grid Checkbox Column alternatives (continue)

Damyan Petev / Monday, August 13, 2012

This is the second part covering the topic of jQuery Grid checkbox column alternatives and/or custom implementations. A quick recap: the Checkbox column is a feature that provides default setup to turn Boolean values in the jQuery grid into checkboxes. As I explained, however, defaults are never good enough for every scenario out there and the community brought up the limitation it has regarding coexistence with column and row templates. While there are valid reasons for that, there are also plenty of cases where  you want to go:

jQuery Grid checkbox column alternative using template to enchance funtionality and match experience.

Well, you are not seeing wrong – it does look essentially the same. It’s very simple really. Say, if an application has been using the checkboxes so far and (as it always happens) an idea emerges to add some additional functionality over existing/new column that requires using a template. So it’s reasonable to want keep the same UI as before, right?

Furthermore, as you may remember from the first blog, I don’t quite enjoy the looks on the default checkbox and god forbid the user is running some other OS… Now these checkboxes look just fantastic, so let’s see how we can use those instead.

The Checkbox column feature to the rescue!

Not seeing wrong again – what I realized while creating the input templates is that the default feature itself is one as well! The feature being there, the styles and other useful bits are there for us to put together as we see fit. With that said, the first and the easiest thing to reuse is the checkbox column template and its styles.

The template

That template uses styled HTML Span tags along with CSS to create that UI element, which means it is completely open for styling and independent of OS or browser implementation. Here’s how a template like that would look like:

  1. var checkboxTemplate = "<span style=\"width:100%;display:inline-block;overflow:hidden;text-align:center;\">" +
  2.     "<span class=\"ui-state-default ui-corner-all ui-igcheckbox-small\">" +
  3.     "<span class=\"ui-icon ui-icon-check ui-igcheckbox-small-{{if ${SalariedFlag} === \"true\"}}on{{else}}off{{/if}}\"/>" +
  4.     "</span></span>";

Now the structure for the spans:

  • The outermost Span acts as a cell container spreading itself in the space available and allowing for the inner ones to be nicely centered. Optional.
  • The middle one acts as the checkbox border. Required.
  • The innermost is the empty/tick filling. The “ui-igcheckbox-small-off/on” are the respective styles. Note that the ‘off’ style overrides the ‘on’, so you can essentially have ‘on’ by default and add/remove ‘off’ as appropriate (that is exactly how it will be done later on). Also if you have noticed the ‘small’ component, there’s also a ‘normal’ version you can see in the Row Selectors feature. Required.

The result from this are not surprising (we were after those anyway). The best part is that you get the same look as before(if you have been using the feature), you get an UI control that looks much better than the default and even bleds with the style of other controls. Moreover, it does not only fit in style, it matches the style of the Row Selectors (which I view as essential is both are used) and it matches the theme, since the looks come from the CSS and it comes from the theme. A sample grid definition using the above:

  1. $.ig.loader(function () {
  2.     $("#grid").igGrid({
  3.         primaryKey: "BusinessEntityID",
  4.         height: 550,
  5.         dataSource: "@Url.Action("Employees")",
  6.         autoGenerateColumns: false,
  7.         columns: [
  8.             { key: "BusinessEntityID", width: "50px", headerText: "ID", dataType: "number" , template: "<a href=\"http://msdn.microsoft.com/en-us/library/ms124432(v=sql.100).aspx\">${BusinessEntityID}</a>"},
  9.             { key: "LoginID", width: "250px", headerText: "Login ID", dataType: "string" },
  10.             { key: "JobTitle", width: "220px" , headerText: "Job Title", dataType: "string" },
  11.             { key: "SalariedFlag", width: "120px", headerText: "SalariedFlag", dataType: "bool", template: checkboxTemplate },
  12.             { key: "CurrentFlag", width: "100px",headerText: "Current Flag", dataType: "bool" , template: "<span style=\"width:100%;display:inline-block;overflow:hidden;text-align:center;\"><span class=\"ui-state-default ui-corner-all ui-igcheckbox-normal\"><span class=\"ui-icon ui-icon-check ui-igcheckbox-normal-on{{if ${CurrentFlag} === \"true\"}} {{else}} ui-igcheckbox-normal-off{{/if}}\"/></span></span>"}
  13.              ],
  14.         features: [
  15.             { name: "Filtering", mode: "advanced", type: "local" },
  16.             { name: "Sorting", type: "local" },
  17.             { name: "Updating", editMode: 'none'}]
  18.     });
  19. });

The resulting grid with both small (default) checkboxes on the ‘Salaried’ column and ‘normal’ on the “Current Flag”:

Checkbox columns using the default feature's template.

And, yes, they go Metro or Modern UI (guess that is how it will be called now) if you apply the ‘metro’ theme:

Checkbox columns using the default feature's template with metro / modern theme applied.

As you can see since the values are Boolean the columns have no trouble with other Grid features.

One-click inline value updates

Of course, just like the previous solution you can hook up to the click event to handle user interaction without entering editing mode. Like before, using the Updating API will require the feature itself to be loaded and initialized with the grid (NetAdvantage® jQuery Online Help : igGrid Updating). This time, however we have two columns this time. The options are two – duplicate the event handler for each column or simply pass on the column key to the function and use that instead of static value:

  1. var checkboxTemplate = "<span style=\"width:100%;display:inline-block;overflow:hidden;text-align:center;\">" +
  2.     "<span class=\"ui-state-default ui-corner-all ui-igcheckbox-small\">" +
  3.     "<span class=\"ui-icon ui-icon-check ui-igcheckbox-small-{{if ${SalariedFlag} === \"true\"}}on{{else}}off{{/if}}\"" +
  4.     " data-rowid=\"${BusinessEntityID}\"  onclick=\"checkboxChanged(event, 'SalariedFlag');\"/></span></span>";

The ‘data-‘ attribute will be used to keep track of the row key and the column is passed to the handler. Some minor tweaks regarding the different availability of properties for browser support and the final snippet looks like this:

  1. function checkboxChanged(evt, colId) {
  2.     // for IE < 9 currentTarget is srcElement
  3.     var element = evt.currentTarget || evt.srcElement;
  4.     // get rowID where change occured:
  5.     var rowId = $(element).data().rowid;
  6.     var newValue;
  7.     //IE doesn't have classList so check and use Name instead
  8.     if (element.classList){
  9.         newValue = element.classList.contains("ui-igcheckbox-small-on") ? false : true;
  10.     }
  11.     else{
  12.         newValue = element.className.indexOf("ui-igcheckbox-small-on") >= 0 ? false : true;
  13.     }
  14.     $("#grid").igGridUpdating("setCellValue", rowId, colId, newValue);
  15. }

The idea is very simple – if click has occurred it means the value should change and it should be the opposite of what it was right now – so we check if ‘on’ style is applied. Note, that I mentioned ‘off’ is the overwriting one, however in this case updating the cell value with an Updating method causes the UI to update and therefore the template is reapplied and the ‘on’ style won’t be there if the value is false.

Editing Action

There’s one more awesome part of the Checkbox column feature that can be reused. In the first blog I mentioned you can define your own Editor Provider. If you look up the Grid Updating API reference you can see a ‘columnSetting’ that allows to set an Editor Provider and it “should extend $.ig.EditorProviderDefault or it should have definitions of all its methods”. Now that can take some coding, but guess what – the Checkbox column already has one defined! So this is how you use it:

  1. $.ig.loader(function () {
  2.     $("#grid").igGrid({
  3.         primaryKey: "BusinessEntityID",
  4.         height: 550,
  5.         dataSource: "@Url.Action("Employees")",
  6.         autoGenerateColumns: false,
  7.         columns: [
  8.             { 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>"},
  9.             { key: "LoginID", width: "250px", headerText: "Login ID", dataType: "string" },
  10.             { key: "JobTitle", width: "220px" , headerText: "Job Title", dataType: "string" },
  11.             { key: "SalariedFlag", width: "150px", headerText: "SalariedFlag", dataType: "bool", template: checkboxTemplate}
  12.         ],
  13.         features: [
  14.             { name: "Filtering", mode: "advanced", type: "local" },
  15.             { name: "Sorting", type: "local" },
  16.             { name: "Updating", columnSettings: [{ columnKey: "SalariedFlag", editorProvider: new $.ig.CustomEditorProviderCheckbox()} ]}]
  17.     });
  18. });

At line 16 – simple and very effective. Now when you enter edit mode you get a proper (and consistent) experience. And it’s really that simple:

The editor created by the checkbox provider.

Editor provider

The default provider shown above doesn’t quite meet the requirements? Then we’ll use a custom one and throw in some non-Boolean values in the mix for a good measure. As explained, you have a whole bunch of Editor provider for the needs of updating along with a default one that is recommended for customization. In our case, however, we can extend the checkbox provider instead and only implement the differences. Let me explain the basics of a provider – it’s a class with a few essential methods – one for creating an editor (this one gets called by Updating when edit mode gets activated and returns the actual editing element), one for getting and one for setting values. Then you have focus, size and validation managing, but those are implemented or don’t concern our type of provider. This is the basic flow of method execution to give you an idea why each method does what it does:

Generalized flow of events and resulting method calls for the Updating editors.

For example, if again you need to support values different than Boolean, you can use your template as usual and override the get and set value methods of the provider to have a nice checkbox editor as well – no read-only and no flicker. Plus it’s even quite easy to implement as well. For the case from the last blog and any other case of random ‘acceptable’ values and false equivalents means we don’t need to modify the setValue method as..well numbers and strings will evaluate as true and null-s will evaluate as false and it’ll work just fine to display the value in the editor. So it’s just the get and it’s almost amusingly easy:

  1. $.ig.CustomEditorProviderCheckbox = $.ig.CustomEditorProviderCheckbox || $.ig.EditorProviderCheckbox.extend({
  2.     getValue: function () {
  3.         return this.value ? 10 : null;
  4.     }
  5. });

Now say that those values won’t evaluate as expected (even though they currently do), you will have to also override the setValue and call the super-class’ method with the desired output:

  1. $.ig.CustomEditorProviderCheckbox = $.ig.CustomEditorProviderCheckbox || $.ig.EditorProviderCheckbox.extend({
  2.     getValue: function () {
  3.         return this.value ? 10 : null;
  4.     },
  5.     setValue: function (val, updating) {
  6.       val = parseInt(val) ? true : false;
  7.       this._super(val, updating);
  8.     }
  9. });

And if all that is not enough you can always go further – change the UI rendered for the Editor in the create method, change the way  interaction affects the value in the set method and so on. But that’s a whole separate topic or more like a topic per type of provider as most of them have their special requirements and implementations.

The rest is pretty clear – same template style as in the ‘Editing Action’ part only with parseInt instead like in the previous blog. And again, using the column settings for Updating you set the provider of the desired column to be a new ‘$.ig.CustomEditorProviderCheckbox()’.

Comparison

PROS
  • Will work with other templates.
  • Easy to implement - 1 line for simple scenarios or one more to set the provider.
  • Consistent looks – fits with the rest of the grid parts and matches theme.
  • Excellent editing by reusing the default checkbox editor provider.
  • Again not limited to just Boolean values.
  • Also not limited to a bi-state checkbox, an tri-state checkbox can be potentially implemented(JSFiddle showing a simple 3-state checkbox implementation.)
CONS
  • Can require some code to implement with non-Boolean values, although as shown even there you can spare yourself most of the work.
  • Does require a little work to add that ‘single-click update’ functionality. I could argue it’s well worth it.
  • Nothing else I can see, really.

Conclusions

I hope this has been educational and occasionally useful. I also hope that it’s been made clear that defaults are not your only option and not only is it easy to create custom solutions but also recycling parts of available ones makes it that much better. The way the data is represented is completely arbitrary from the actual values and not only the good old forms elements or the modified ones with consistent looks can be used – at this point you can replace the checkbox with slider/toggle button and you can use similar approach to modify any other type of data representation. Because, in the end, the jQuery Grid and its features have proven to be very customizable and in most cases always have the right property, method or event to help a developer create awesome experiences!

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!