jQuery Editors: Rich client experience (part 2)

Damyan Petev / Thursday, September 20, 2012

jQuery editors rich client experienceTime for some more fiddling with the elaborate API of the jQuery Editors helping you deliver awesome experiences! Here’s the quick jump list for the previous articles:

Still spinning

Well there’s one more trick you can do with the spin buttons and the dropdown too – you can make the editor read only (no not disabled like in the first article). The trick with read-only editor is that, yes – the field itself will not accept input, but the buttons still work just fine! And as you’d imagine there’s a property to allow interaction with those to still change the value and I also find useful the spin event to make it crystal clear to the user what happened exactly:

  1. <label>Units in stock:</label>
  2. <input id="stock" />
  3. <label style="height: 0; display: none;" id="stockInfo"></label>
  1. $("#stock").igNumericEditor({
  2.     button: 'spin',
  3.     spinDelta: 10,
  4.     readOnly: true,
  5.     spinOnReadOnly: true,
  6.     minValue: 0,
  7.     spin: function (evt, ui) {
  8.        $("#stockInfo").stop(true,true)
  9.        .text(ui.delta).css('color', ui.delta > 0 ? 'green' : 'red')
  10.        .fadeIn(200).delay(100).fadeOut(200);
  11.     }
  12. });

And the result of that is:

jQuery editor with spin buttons, delta and event handle

So, you can have an edit field and make sure without any need to check that the values can only go within a range and they can change by exactly as much as you want and in the meantime add a cool and helpful effect for the user (if you are changing the delta in runtime). Check out the demos – there you can find another field with spin buttons (showing the product category) and since that is just an ID and the actual category name is in a separate table, using events like this you can make calls to get the name of the category to display so the user can actually make sense of the numbers.

On a side note, to clarify the difference between this and disabled – when you disable all interactions are cut off, while a read-only input still can be given focus, interacted with and fire events and such.

A basic combo

I mentioned a few times so far you can create editors with dropdowns instead of spin buttons and they behave much like a basic combo box would with plenty of features. Let’s take the spin-button example from last article and use it with a dropdown. One neat trick is that when you provide the list of items it doesn't have to be strings – can be objects with ‘text’ property to be used instead. And if those objects happen to have a ‘getHtml(')’ method – the result of it will be the representation of the item in the combo. That means you can produce amazing template combos and still get the auto-complete functionality for text like you would with the spins:

  1. <label>Autocomplete field(pick a product):</label>
  2. <input id="autoText" />
  3. <script type="text/javascript">
  4. // the data object and an array of it:
  5. function textObj (txt){
  6.     this.text = txt;
  7.     this.getHtml = function(){
  8.         return "<p>" + this.text + "&nbsp  (<a href='http://www.infragistics.com/products/" + this.text + "' style=\'color: #000;\' target='_blank'>read more</a>) </p>';
  9.     };
  10. }
  11. var textData = [new textObj("aspnet"), new textObj("jquery"), new textObj("ios"), new textObj("reporting"), new textObj("wpf"), new textObj("sharepoint")];
  12.  
  13. $.ig.loader(function () {
  14.     $("#autoText").igTextEditor({
  15.         listItems: textData,
  16.         button: 'dropdown',
  17.         listMatchOnly: true,
  18.         validatorOptions: {
  19.             onblur: true,
  20.             onchange: true
  21.         }
  22.     });
  23. });
  24. </script>

The list of Infragistics products is obviously incomplete, it was random, sorry :\

jQuery text editor with dropdown!

Runtime localization

Except shipping you page/app with controls that are already set, the editors handle dynamic regional settings quite gracefully as well. To have access to the required localization resources you would need either the specific regional settings you’d want to use or form simplicity the whole bundle. For detailed information loop up the localization section in the Using JavaScript Resources in NetAdvantage for jQuery  and the Customizing the Localization of NetAdvantage for jQuery Controls one. For this simple case all that is needed is the resource file and I will just let the Loader figure when/where those should be placed. You can do that by adding a path to the file:

  1. $.ig.loader({
  2.     scriptPath: "http://cdn-na.infragistics.com/jquery/20121/2049/js",
  3.     cssPath: "http://cdn-na.infragistics.com/jquery/20121/2049/css",
  4.     resources: "igEditors,igValidator,modules/i18n/regional/infragistics.ui.regional-i18n.js,igCombo",
  5.     theme: 'metro'
  6. });

or use the intended regional property (and yes you can put the bundle ‘i18n’ file there too!):

  1. @(Html.Infragistics().Loader()
  2.     .CssPath("http://cdn-na.infragistics.com/jquery/20121/2049/css")
  3.     .ScriptPath("http://cdn-na.infragistics.com/jquery/20121/2049/js")
  4.     .Resources("igEditors,igValidator,igCombo")
  5.     .Regional("i18n")
  6.     .Theme("metro").Render())

Now you have all the local setting s available at any point and all editors can have regional settings and you can apply any at any point. With some event handling you can do that and achieve quite interesting results with some controls, like for example the Currency editor, which you can turn into a currency conversion field:

  1. <div id="details">
  2.     <label>Price in:</label>
  3.     <input type='radio' name="currency" value="en-US" checked />
  4.     <label>USD</label>
  5.     <input type='radio' name="currency" value="bg" />
  6.     <label>BGN</label>
  7.     <input type='radio' name="currency" value="en-GB" />
  8.     <label>GBP</label>
  9. </div>
  10. <label>Unit price:</label>
  11.      <input id="price" />
  12. <script type="text/javascript">
  13. var USDto1GBP = 1.6190, BGNto1USD = 1.51;
  14.  
  15. $.ig.loader(function () {
  16.     $("#price").igCurrencyEditor({
  17.         maxValue: 10000,
  18.         minValue: 1,
  19.         regional: "en-US",
  20.         valueChanged: function (evt, ui) {
  21.             //store base (USD) value in the element
  22.             var regional = $('input:checked').val();
  23.             switch (regional) {
  24.                 case "en-US":
  25.                     $("#price").data('value', ui.value);
  26.                     break;
  27.                 case "bg":
  28.                     $("#price").data('value', ui.value / BGNto1USD);
  29.                     break;
  30.                 case "en-GB":
  31.                     $("#price").data('value', ui.value * USDto1GBP);
  32.                     break;
  33.             }
  34.         }
  35.     });
  36. });
  37.  
  38. //event handler for all 3 radio-s
  39. $('#details :radio').change(function myfunction(evt) {
  40.     var index = $('#details :radio').index(this);
  41.     switch (index) {
  42.     case 0:
  43.        $("#price").igCurrencyEditor("option", "value", $("#price").data('value'));
  44.         break;
  45.     case 1:
  46.         $("#price").igCurrencyEditor("option", "value", $("#price").data('value')*BGNto1USD);
  47.         break;
  48.     case 2:
  49.         $("#price").igCurrencyEditor("option", "value", $("#price").data('value')/USDto1GBP);
  50.         break;
  51.     }
  52.     //apply regional
  53.     $("#price").igCurrencyEditor('option', 'regional', this.value);
  54. });
  55. </script>

This is a-a-almost the full snippet needed to get this to work (just missing the loader from previous snippet :) ). Of course, as you can see the exchange rates are static (hey, they are up to date as of the moment of writing at least), but you can easily pull those from some currency exchange API out there. In this scenario values are actually fed to the editor in USD and that is the base value that is always retained – the rest are calculated from it and on change back to it when different regional is applied:

jQuery currency editor with dynamic regional settings

Make sure you check out one the demos and give this one a try yourself.

Enhancing the validation

Hiding the validation error message

I find this to be particularly useful, because error messages tend to stick around until valid value is entered, even when you don’t want to fix it right now. That’s especially annoying when you have other fields around stacked quite tightly (like I did) and that error message just gets in the way of something else. Not to worry! I have a small snippet to solve that globlally – hooking up to the igValidator event fired when the message is shown and setting a timer for it to be hidden!

  1. //hide error message cuz its evil >:D
  2. $(document).on("igvalidatorerrorshown", function (evt, ui) {
  3.     setTimeout(function () {
  4.         //but keep the error'd css
  5.         ui.owner.hide(false);
  6.     }, 2500);
  7. });

The best part is since each editor initializes its own validator instance those timers don’t interfere with each other and since passing false makes the hide skip removing the specific CSS the editor remains with red borders to remind you that it’s still something wrong. A possible improvement on this would be to handle the focus event and show the message back then, not after another validation pass.

Making use of events

I have this quite lovely example (in my eyes at least, so bear with me) of a Mask editor functioning somewhat like a Hangman game, well, for the limited attempts at the very least. While this doesn’t entitrely have to be a copy-past kind of a deal, like you could use the message hiding, I do believe a more adaptive validation – being able to react to a certain number of bad attempts without the server participation is actually quite  - might be quite nice. This is to showcase the capabilities the events and their arguments give you. Here’s what we have:

  1. <label>Hangman (hint: IG):</label>
  2. <input id="mask2" />
  3. <label id="value"></label>
  4. <script type="text/javascript">
  5. $.ig.loader(function () {
  6.     $("#mask2").igMaskEditor({
  7.         
  8.         inputMask: ">ILLLLLILLILL",
  9.         dataMode: 'rawTextWithRequiredPromptsAndLiterals',
  10.         emptyChar: "_",
  11.         valueChanged: function (evnt, ui) {
  12.             $("#value").text("Value: " + $("#mask2").data("igMaskEditor").value());
  13.         },
  14.         validatorOptions : {
  15.             attempts: 4,
  16.             onblur: true,
  17.             onchange: false,
  18.             checkValue: function (evt, ui) {
  19.                 if (ui.value !== "INFRAGISTICS") {
  20.                     if (!ui.owner.options.attempts) {
  21.                          ui.message = "No attempts left";
  22.                          $(ui.owner.element).igMaskEditor("option", "disabled", true);
  23.                          return false;
  24.                     }
  25.                     ui.message = "Try again. Attempts left: " + (--ui.owner.options.attempts + 1);
  26.                     return false;
  27.                 }
  28.             }
  29.         }
  30.     });
  31. });
  32. </script>

using events to control and adapt the validation

Since when providing options for the validator those are added to the widget itself, you can in essence add your own, like I did here and use it as an internal counter, rather than keeping track of that in external variable(s). Also notice that this is the ‘check-value’ event, not the validation one. This happens before the value is evaluated, and well, you can do that yourself and if not to your liking, even though it would otherwise satisfy the rules, returning false will cause the validator to consider that invalid.

This method can be very useful to recreate real world validation purely on the client – limited number of attempts and when the user fails too many times you redirect him to a different page or display additional verification fields (The ‘prove you are human’ Twitter test, for example, load a Captcha..).

MVC  Gotcha-s

So besides the tricky part with all editors being of the base class type with the ASP.NET MVC helper and having the markup generated for you (which is sometimes different than you might assume), there’s some more quirks.

As you’d imagine you can’t go out of your way and add random attributes to the validator options with the helpers like it’s done above. You can still do it in simple script tag one the widget has been initialized. Also just for now you can’t really pass the ‘item lists’ array of objects as it kinda expects a List<string>.

One other notable gotcha – when dealing with regular expressions (and I might have totally missed that one before). By default the relevant validator property accepts both a string or the ready Regexp object. However MVC helpers, being helpful as they are, write strings to the document, therefore eating your escapes. And the parsing of the string to RegExp calls to string and it eats up another escape..so to speak. So if you are using sequences that need to be escaped once in a normal regular expression like ‘\b’ those must be escaped three times in the wrapper:

  1. @(Html.Infragistics().TextEditor().ID("qpu").Required(true)
  2. .ValidatorOptions(validate =>
  3.             validate.CustomErrorMessage("Should contain a numerical value for quantity, followed by space and a measuring unit. <br><em>Example: 20 kg </em>")
  4.         .RegExp("\\\\b[0-9]+ \\\\b[A-z]").OnBlur(true).OnChange(true))
  5. .Render())

Basically this reaches the client as ‘\\b’ and after another call of toString() and up as the proper ‘\b’ to be converted to expression.

Demos and resources

  • You can fiddle with two demos of your choosing:

- A JSFiddle demo, where the submit button is only good for triggering validation.

As always, you can follow us on Twitter @DamyanPetev and @Infragistics and stay in touch on Facebook, Google+ and LinkedIn! Stay tuned some pretty exciting stuff are happening for the next release!

TL;DR | Summary

Don’t say I didn’t warn about the jQuery Editors API – the fact is it’s quite a lot, it has to provide support for several different controls after all! As in the latest series you’ve seen how much you can tweak and drastically change the experience – rich client-side editing with no postbacks, flexible validation that you can even mix with your additional logic to create adaptive behavior, or make a behavior for invalid values. Even the little oddities of the ASP.NET helpers are no problem once you get to know them and perhaps we’ll continue soon with some more MVC editor fun..!