Tuesday, November 27, 2012

Guiding User Entry using jQuery Input Masks

Providing masked input entry on a form is an excellent way to guide the user to both enter correct values and require less keystrokes to make those entries. The challenge is determining the boundaries of the functionality the mask provides to achieve various formatted entry. As I worked with the different options available, I quickly found different limits that each library was capable of providing. I've broken down the features I was interested in enabling into three main groups:


  • Masks with fixed formats and no range validation on the user entry other than numeric and/or alpha characters. Phone numbers, tax ID, serial numbers, etc all fall into this group. Its the simplest implementation that is basically only interested in preserving a specific format and forcing certain characters to be entered.

  • Masks with fixed formats with specific range validation on each "field" of the mask. Dates and times are the two most common examples. Each part of the date/time must fall in a certain numerical range to be valid. Dates are further complicated by dependencies on other parts of the date (ie number of days in a month and leap years).

  • Formatted entries that are not really a mask but can use some of the logic provided in the mask library to manage the input. Currency and numbers are the primary concerns in this case. Providing visual cues for thousand separators, decimals, etc. provide helpful feedback to the user when entering larger numbers. You don't want to really show a mask for the entry - you just want the number to format as the user enters the value.



As I evaluated different libraries, I attempted to recreate the functionality outlined above. I started by looking at the options available in the jQuery UI library since I'd like to use masked entry in conjunction with other jQuery UI widgets. At this point, there is no production version of a mask control. On the development wiki, a mask widget is discussed and its basic functionality defined. As I dug through GitHub, I did find it in a separate branch. Given that its clearly not ready for production use, I needed to find a suitable alternative.

I started by trying out Josh Bush's (digitalbush.com/projects/masked-input-plugin) mask plugin. It's functionality is very similar to that described by the Wiki page. One common feature I found both libraries do not have is more advanced control input constraints on the mask. For instance, you can create a mask for dates (99/99/9999) but it does not constrain the range of numbers to only actual dates. Additionally, it does not auto-format the numeric entry with thousand separators, dollar signs, etc.

After some further searching, I found RobinHerbots/jquery.inputmask which actually credits Josh's work as the foundation for the plugin. This more robust implementation has a lot more extensibility. Out-of-the-box, it can understand dates, numeric values, and provides sufficient hooks to add more. One of the features that impressed me was the auto-complete logic for dates and times. If you type 2512 in an input with "m/d/y" specified as the mask, it will properly expand to 02/05/2012. Additionally, the time mask will take 22 hours and convert it to 10 PM. The plugin also has a "decimal" mask that will auto-format numeric entries as they are typed and constrain the values appropriately. When testing the numeric input mask, I had issues with the numericInput option which is suppose to right along the text in the input box. Whenever I'd try to click on the decimal, it would jump to the integer portion of the value. Turning that off, fixed the issue. I also had issues trying to extend the decimal mask to add a dollar sign to the input and integrating it with a jQuery UI spinner. The documentation is a little thin so I may just not be entirely grasping the concept of how to extend it further.

Looking at these different options made me wonder whether there may be an argument to split the physical masking component from the actual validation of the entry. Even though, by definition, the mask is providing a certain degree of validation (only numbers, letters, etc), its main purpose is to provide the formatting so the user is not required to provide it. This is both a short-cut and a method to ensure consistent entry. However, I still believe its important to attempt to provide validation cues to the user as quickly as possible to help guide proper entry. If it can be done while entering parts of a value (like a the month or day in a date), then the user will less likely be required to revisit a field when the final form validation is performed.

As an experiment, I created a demo in my sandbox which uses the digitalbush library to provide the base mask functionality. I extended the library to recognize the different fields of the mask and request validation of each field as the user enters the values into the mask. This functionality allowed me to provide date/time validation similar to the RobinHerbots/jquery.inputmask implementation. The setup of the mask for the date includes the basic mask (99/99/9999) and then a validation function to handle the range checks:


$("#date2")
.mask(
'99/99/9999',
{ validate: function (fld,cur) {
// 0 == month; 1 == day; 2 == year
var mm = parseInt(fld[0]),
dd = parseInt(fld[1]),
yy = parseInt(fld[2]),
vl = true;

if (!(mm >= 0 && mm < 13) && cur == 0) {
fld[0] = '12';
vl = false;
}

if (!(dd >= 0 && dd < 31) && cur == 1) {
fld[1] = '01';
vl = false;
}

if (!(yy >= 1976 && yy < 2199) && cur == 2 && fld[2].replace('_','').length == 4) {
fld[2] = '2012';
vl = false;
}

return vl;
}
});


Its not a perfect implementation - it doesn't handle the dependencies of number days in a month and leap year. It would also make sense to move the validation into a global function in the library (ie $.mask.validate.date) so it could easily be reused and provide different validation for other formats.

For formatted numeric fields, I mainly used the Globalize library to validate and format the entry on each keystroke. I only used the caret() plugin provided in the digitalbush library to help place the cursor in the correct spot in the field as the entry was made:


var old = '';
$("#decimal2")
.on('focus', function (e) {
old = '';
})
.on('keydown', function (e) {
old = $(this).val();
})
.on('keyup', function (e) {
var n = Globalize.parseFloat($(this).val()),
pos;

if (isNaN(n)) {
pos = old.indexOf('.');
$(this).val( old )
.caret( pos, pos);
} else {
n = Globalize.format( n, 'n' );
pos = n.indexOf('.');
$(this).val( n )
.caret( pos, pos);
}
});


Again, a fairly rough implementation but it provides a template for how to approach the problem. The mask plugin already is attached to these events and with some tweaking could be augmented to provide the cursor control but not enforce a fixed mask for the input. Globalize already has a substantial amount of logic to handle numeric parsing/formatting across multiple locales so it makes sense to utilize this functionality in this situation.

Using masked input makes a lot of sense to help guide the user to enter correct data with minimal effort. While there is some work required to achieve certain features, the foundation exists to build these components. It might be awhile before the jQuery UI implementation is ready, but there are some good alternatives available that provide similar functionality that you can expect to see in the final jQuery version.

Tuesday, November 20, 2012

Selecting Ranges with the jQuery UI Datepicker

The current supported method of selecting a date range with the jQuery UI Datepicker is to use two pickers which represent the start and end of the range. In certain scenarios, where there is a large range, using two fields is a preferable choice. However, for shorter ranges, having the ability to allow the user to pick it from one box seems like a feasible alternative.

The Google Analytics report date filter takes a kind of hybrid approach. The UI has one field representing the range with a drop down menu that shows several different range selection options. There are two fields to enter a range but you can also select the range from the picker and achieve the same result. Its a fairly specific use case but an interesting approach worth considering:



I did find a jQuery plugin that provided this feature at eyecon.ro/datepicker/. I tested the plugin to see how it worked and ran into several issues. Typically, you don't have to specify any options to use a widget, however, minimally, you need to specify the date option otherwise it will throw an error. Additionally, the code referenced the depreciated jQuery.curCSS(). That needed to change to get it to work with a more recent version of jQuery. There are some other quirks as well - its last build was in 2009 so its probably not as well maintained as one would like. Regardless of the issues, the demos provided some ideas on how to modify the jQuery Datepicker to provide a similar feature.

With a little work, I was able to get the jQuery UI Datepicker to select more than one date. It was a little frustrating trying to deal with the two modes of operation - inline in a DIV or attached to an input field. In the former, the picker is always visible while, in the latter, it only appears when focusing on the input and hides once the user selects a date. I unfortunately needed a hybrid behavior:


  • Show the picker when the input receives focus. However, don't hide the picker once a date is selected, instead stay visible to allow the user to pick a second date to complete the range.

  • Show the selected range in the picker by highlighting the date included in the range. Additionally, update the input field with the selected range as the user picks different dates.

  • Only hide the picker when the user explicitly says they are done selecting the range.



The first feature can be accomplished using an inline picker, manually positioning it under the input field, and handling the binding to the focus event outside of the pickers built-in functionality. Here's the setup:


<div id="jrange" class="dates">
<input />
<div></div>
</div>




// global variables to track the date range
var cur = -1, prv = -1;

// Create the picker and align it to the bottom of the input field
// Hide it for later
$('#jrange div')
.datepicker();
.position({
my: 'left top',
at: 'left bottom',
of: $('#jrange input')
})
.hide();

// Listen for focus on the input field and show the picker
$('#jrange input').on('focus', function (e) {
$('#jrange div').show();
});



That part is pretty straight forward - it mimics the functionality that would happen automatically if the picker was attached to the input field. However, since I don't want the picker to hide once a date is selected or have that date set in the input field, I have to attach it inline to the DIV.

The next step is to use several of the callback function available in the Datepicker widget to manage the range selection. Two variables are needed to capture the current day selected and remember the last day selected. Each time a day is selected, the onSelect callback is called. The bulk of the logic needs to go in this function:


$('#jrange div')
.datepicker({
onSelect: function ( dateText, inst ) {
var d1, d2;

prv = +cur;
cur = inst.selectedDay;
if ( prv == -1 || prv == cur ) {
prv = cur;
$('#jrange input').val( dateText );
} else {
d1 = $.datepicker.formatDate(
'mm/dd/yy',
new Date( inst.selectedYear, inst.selectedMonth, Math.min(prv,cur) ),
{}
);

d2 = $.datepicker.formatDate(
'mm/dd/yy',
new Date( inst.selectedYear, inst.selectedMonth, Math.max(prv,cur) ),
{}
);
$('#jrange input').val( d1+' - '+d2 );
}
}
});


This code will update the global range variable and update the input box with the current selected range (or just the one date if only one day is selected). This captures the range but what about showing it on the actual picker? The beforeShowDay callback provides the necessary hook to achieve this feature. Its called for each day in the picker and enables you to set a class on the TD that represents the date passed in as a parameter:


$('#jrange div')
.datepicker({
beforeShowDay: function ( date ) {
return [true,
( (date.getDate() >= Math.min(prv, cur) && date.getDate() <= Math.max(prv, cur)) ?
'date-range-selected' : '')];
}
});


Once the class is set, a small bit of carefully crafted CSS styles are needed to set the background color of the links in the table cells:


.date-range-selected > .ui-state-active,
.date-range-selected > .ui-state-default {
background: none;
background-color: lightsteelblue;
}


So far, I have been able to utilize all the built-in options of the Datepicker to make this feature work. However, hiding the picker proved to be a challenge. Since, internally to the picker, it is considered inline, any logic that would allow the picker to be hidden is disabled. This includes clicking outside the picker, the close button that can optionally be enabled, or any other method normally available when the picker is attached to the input field. I decided to enable the button line and manually add the close button back onto that line myself. Unfortunately, it can't be done only once when the widget is created because the HTML for the picker is constantly being regenerated which would immediately remove the button. Instead, I added an option for an onAfterUpdate callback to the Datepicker and added a proxy to the internal _updateDatepicker function to call that callback. This will enable me to add the button back each time the calendar HTML is rebuilt:


$.datepicker._defaults.onAfterUpdate = null;

var datepicker__updateDatepicker = $.datepicker._updateDatepicker;
$.datepicker._updateDatepicker = function( inst ) {
datepicker__updateDatepicker.call( this, inst );

var onAfterUpdate = this._get(inst, 'onAfterUpdate');
if (onAfterUpdate)
onAfterUpdate.apply((inst.input ? inst.input[0] : null),
[(inst.input ? inst.input.val() : ''), inst]);
}


The implementation of the callback simply adds the same button HTML that the widget would create if it were in non-inline mode:


$('#jrange div')
.datepicker({
onAfterUpdate: function ( inst ) {
$('<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" data-handler="hide" data-event="click">Done</button>')
.appendTo($('#jrange div .ui-datepicker-buttonpane'))
.on('click', function () { $('#jrange div').hide(); });
}
});


The full source of this implementation and a demo are on my sandbox. There are probably some improvements that can be made to make it easier to reuse the customizations and allow it to work properly with other non-range pickers. However, its a good proof of concept that illustrates how to provide range selection in one Datepicker widget.

Wednesday, November 14, 2012

CSS3 3D Transforms: Rotating Around All the Axises

It seems like a natural evolution to add 3D abilities to the browser.  If you're going to perform 2D transforms.  Why not add 3D transforms?  While not fully supported, 3D transforms are fun and when used appropriately, can really enhance the user experience.

I've been avoiding 3D simply because of the lack of support (IE9 and Opera do not support 3D - see this site for the current support matrix).  However, for fun, I decided to try it out and see how it works.  From a UI perspective, the carousel pattern seems to be a good place to use 3D.  As I was looking for examples, I found this site which has an implementation.

As I looked over the code, I noted several things:

  1. The order of the transform is important.  The wrong order will cause elements to align in unexpected places.

  2. The perspective style will affect the look of the 3D effect.  Most likely, setting it on a container and using transform-style: preserve-3d will be desirable.

  3. Using transform-origin can make it a lot easier to rotate elements in 3D space.



In my sandbox, I created a test to tinker with the different 3D styles to see how they worked. The basic setup is to create a wrapper DIV which will have the perspective style set to some value between 200px and 1000px. Each child DIV will then use transform-style: preserve-3d to inherit the perspective. Additionally, the transform-origin is set so all transforms occur relative to the center of the element but behind the element (by default it is -100px behind each element). Once all the elements have a common transform point established, a rotation transform is applied on one of the DIVs on a specific axis. In this example, the "outer" DIV is rotated around the x-axis, the "inner" DIV is rotated around the z-axis, and the "box" DIVs are rotated around the y-axis.


<div class="wrapper">
<div class="outer">
<div class="inner">
<div class="box">One</div>
<div class="box">Two</div>
<div class="box">Three</div>
<div class="box">Four</div>
<div class="box">Five</div>
</div>
</div>
</div>


The result enables rotating all the elements around the established point of origin on the different axises. There are probably different ways to achieve the same result. The carousel example I linked to above translated the elements on the z-axis to position them properly in space. I prefer using the transform-origin style since the browser does the math for me and makes the code much simpler write and maintain.

While probably not something to use extensively, 3D transforms certainly can add some nice transition effects. If its used appropriately, the transitions could degrade well enough that users visiting with browsers that do not support 3D will still be able to access content. This might require some creative coding or even browser support checks. However, over time, the features will become mainstream and enable a whole new dimension to page design.

Tuesday, November 13, 2012

Using Raphael Sets to Mimic SVG Groups

Raphael provides a nice library for simplifying working with vector graphics. One of its strengths is to work with both SVG and VML making it compatible with older versions of IE. However, with that compatibility comes some sacrifice in features. For instance, you are not able to define SVG groups with the Raphael API. Instead, you use the sets feature to mimic some of the functionality available with the grouping element.

Consider the example on the W3 site related to SVG arcs. This example SVG document does two things - a) it defines a reusable set of SVG objects which are later referenced in the document so they don't have to be declared again, and b) it uses groups to apply transforms to all the contained elements which simplifies the definition.

If you look at the source, you'll see the two ellipses and some text are defined in the "defs" tag:


<defs>
<g id="baseEllipses" font-size="20" >
<ellipse cx="125" cy="125" rx="100" ry="50"
fill="none" stroke="#888888" stroke-width="2" />
<ellipse cx="225" cy="75" rx="100" ry="50"
fill="none" stroke="#888888" stroke-width="2" />
<text x="35" y="70">Arc start</text>
<text x="225" y="145">Arc end</text>
</g>
</defs>


That definition is then reference five times to create the base objects that form the background that other elements are drawn on. The "g" tag is utilized to define each section of the example and translate it to a different point on the screen:


<g transform="translate(400,0)">
<text x="50" y="210">large-arc-flag=0</text>
<text x="50" y="250">sweep-flag=0</text>
<use xlink:href="#baseEllipses"/>
<path d="M 125,75 a100,50 0 0,0 100,50"
fill="none" stroke="red" stroke-width="6" />
</g>


In Raphael, there is not a way to create "defs" and "g" tags. However, you can use the clone() and sets to achieve similar results. I replicated the example SVG document completely with Raphael on my sandbox. The first step was to define the SVG canvas and then create a set that will represent the base ellipse objects that were defined in the "defs" tag:


var r = Raphael( $('.wrapper')[0], '12cm', '5.25cm' ).setViewBox(0,0,1200,525,false),
g1 = r.set(),
g2, attr;

attr = { 'stroke':'#888888', 'stroke-width':'2' };
g1.push( r.ellipse(125,125,100,50).attr(attr) );
g1.push( r.ellipse(225,75,100,50).attr(attr) );

attr = { 'text-anchor': 'left', 'font':'1.3em "Verdana"' };
g1.push( r.text(70,60,'Arc start').attr(attr) );
g1.push( r.text(270,140,'Arc end').attr(attr) );


You can see that the code matches pretty well with the actual SVG document. The difference with the above code is that the objects created will serve as both the first ellipses/text displayed in the top/left of the page and as the cloned object for the remaining four groups. In the SVG example, the "defs" group is not visible - a separate declaration was required to use it.

Next, I used clone() on the set object to create a new set of SVG objects. I then added the specific elements to the group and then transformed them to move the elements to the correct position on the screen:


g2 = g1.clone();

attr = { 'stroke':'red', 'stroke-width':'6' };
g2.push( r.path('M 125,75 a100,50 0 0,0 100,50').attr(attr) );

attr = { 'text-anchor': 'left', 'font':'1.5em "Verdana"' };
g2.push( r.text(150,210,'large-arc-flag=0').attr(attr) );
g2.push( r.text(150,250,'sweep-flag=0').attr(attr) );

g2.transform('t400,0');


If you inspect the elements on the page, you'll see that no "defs" or "g" tags are generated by Raphael. It produces a fairly flat document constructed with the basic SVG objects. The transform operation is applied to all the elements in the set and not just to a containing group tag. The set operations are internal to Raphael and have no real implementation in the DOM. This allows Raphael to select between SVG/VML depending on the browser to ensure compatibility. However, the downside is that you don't get all the native SVG features. The Raphael API does still provide flexible features to manage groups of elements - it just abstracts it from the underlying implementation.

Thursday, November 1, 2012

Finding Points Along an SVG Path using Raphael

In my last post, I discussed how using paths with Raphael is a good thing and that certain functions in the library only work with SVG elements of type "path". Now I'm going to do something a little more complex with paths which will utilize these functions. I've posted a demo on my sandbox which implements a slider like control that is bound to a SVG path. Typically, you'd be restricted to a slider that basically moves over a straight line - up/down or left/right. With this functionality, all kinds of slider controls can be created. With the tools in Raphael, this feature is pretty easy to implement.

First, we need to create a path that will serve as the constraint for our slider knob. There are two types of paths we can create:


  • A closed path that is semi-circular. The knob will continue around from start to finish and start again.

  • A open path that is linear in nature. There is a start and end which the knob can travel back-and-forth between.



While the path does not need to be completely circular or exactly linear, the closer they are to these basic shapes, the better the effect. This is because the points are being estimated based on these shapes. If you look at the demo and compare the circle to the ellipse, you might notice that the knob drifts slight from the mouse pointer as you move around the ellipse whereas the circle does not. This is due to the error in the estimation. This is more evident in closed paths that use the circle estimation than the linear path approximations.

In the example, I created a list of predefined shapes that can be created as reference paths. The following line of code uses the currently selected shape (based on clicking one of the buttons) and adds it to the DOM using the Raphael path() function. I pass the path definition through Raphael.transformPath() because all the paths need to start at a specific place for the logic to work.



path = paper.path( Raphael.transformPath(pdefs[useDef].path, pdefs[useDef].transform) )
.attr( 'stroke-width', 10 )
.attr( 'stroke', 'rgb(80,80,80)' ),



Next, I create a simple slider knob. I purposely created this as an ellipse to show how the shape follows the angle of the path as well as the position on the path. Additionally, I need something to track the current mouse position so I add a DIV and position it directly over the knob object. This ensure all dragging operations are caught by the DIV. I did this so even if you move outside of the path, the knob will keep moving relative to the dragging DIV.



knob = paper.ellipse( 0, 0, 25, 15 )
.attr( 'fill', 'lime' )
.attr( 'stroke', 'rgba(80,80,80,0.5)' ),

$shim = $('<div>')
.css( {position: 'absolute', width: 50, height: 50 } )
.appendTo( $('.wrapper') ),



Now, I need a few other pieces of information about the reference path. I initialize the total length of the path, find the center of the path based on its bounding box, and set the starting point of the knob to the start of the path:



len = path.getTotalLength(),
bb = path.getBBox(),
mid = {x: bb.x+bb.width/2, y: bb.y+bb.height/2},

pal = path.getPointAtLength(0);



The Raphael getPointAtLength() function is a really useful function to find a specific point on the path based on the length along the path. So if you have a circle with a parameter of 100 pixels, getPointAtLength(10) would return a point on the circle 10px from the start. In my demo, if I want to know where to position my slider knob is suppose to be on the reference path, I need to figure out how far along the reference path the proxy DIV has been dragged.

For a circular shape, we can figure this out by converting the Cartesan coordinates to Polar coordinates. This transformation provides an angle around a circle which when divided by 360 can be used as a percentage around the circle. That percentage can be multiplied by the total length of the path to find the length to pass to getPointAtLength().

I illustrated this concept below by drawing a reference circle over the rectangle. The yellow dot represents where the DIV has been currently dragged. Using this point relative to the center of the rectangle, we can find the angle around the circle. For this calculation, 0 degrees is on the positive side of x-axis. This is important and the reason why I transformed the reference paths - I needed their start point to align with 0 degrees. In this example, the point is at the 45 degree angle on the circle. 45/360 = 0.125 which I can multiple by the total length of the path to find the point indicated in the second drawing.



So how do we find that angle? In turns out Raphael has a function that does this - angle(). Pass it two points and it will return the angle (0-360 degrees) the first point is relative to the second point on the imaginary circle. Divide the result by 360 and we have the percentage to multiply by the length to find where to position the slider knob.

All of this explanation turns into these few lines of code:


$shim.draggable({
drag: function ( e, ui ) {

// Find lines and then angle to determine
// percentage around an imaginary circle.
var t = ( Raphael.angle( ui.position.left+25, ui.position.top+25, mid.x, mid.y ) ) / 360;

// Using t, find a point along the path
pal = path.getPointAtLength( (t * len) % len );

// Move the knob to the new point
knob.transform( 't' + [pal.x, pal.y] + 'r' + pal.alpha );
},
stop: function ( e, ui ) {
$shim.css({ left: pal.x-25, top: pal.y-25 });
}
});


I enabled a jQuery UI Draggable on the DIV and implemented the drag callback to use the current position of the DIV to find the angle, get the point via getPointAtLength(), and then use a transform to position the slider knob. Notice that I also rotate the knob based on the value "alpha" return by getPointAtLength(). This value represents the tangent line at the current point on the path. It be used to angle an object to follow along with the curve of the path:





The final step in the drag operation is to move the proxy shim back over the knob so the next attempt to move the knob will trigger the movement logic.

This example highlighted one of the possible uses of the path functionality available in Raphael. I'm sure it only scratches the surface of some of the possibilities. What's nice about the implementation is that it is fast enough to keep up with the mouse move events triggered by the Draggable. I was a little concerned that the math operations involved might make the movement look choppy.

I did not implement the linear option in this demo. It is a different set of calculations to project the current mouse coordinates onto a line that represents the path and find the correct point based on that calculation. Additionally, a circular open path implementation would be interesting as well as some other variations. Those are all little exercises for another day.