jQuery UI is now a main stream user interface toolkit. It works most everywhere, but touch devices currently struggle with some of it’s widgets, including the Slider. The problem is touch and drag events can get mixed up between the web page and the browser. In other words when does a swipe indicate to scroll the page or slider adjustment. Currently after a couple searches there are a couple attempts to fix this without much effort beyond including a javascript file:

Essentially these two plugins listen for touch events and pass the event on to jQuery UI so they can be processed. But still these can be cumbersome in my findings. So I’ve come up with a quick solution.

When we use a slider, and touch events are available we know it is a touch device, so we can insert a couple jQuery UI buttons to increment and decrement the sliders.

Add Accessibility to jQuery UI Slider for Touch Devices

Adding this ability can be as simple as one more method chained after your slider:


$('#slider').slider().sliderAccess();

On top of that if you want it to always show, even when it’s not a touch enabled device, you can specify that as well:


$('#slider').slider().sliderAccess({ touchonly : false });

Ok, so you this is easy enough to use, lets see the goods:


/*
 * jQuery UI Slider Access
 * By: Trent Richardson [http://trentrichardson.com]
 * Version 0.2
 * Last Modified: 12/12/2011
 *
 * Copyright 2011 Trent Richardson
 * Dual licensed under the MIT and GPL licenses.
 * http://trentrichardson.com/Impromptu/GPL-LICENSE.txt
 * http://trentrichardson.com/Impromptu/MIT-LICENSE.txt
 *
 */
 (function ($) {

 	$.fn.extend({
 		sliderAccess: function (options) {
 			options = options || {};
 			options.touchonly = options.touchonly !== undefined ? options.touchonly : true; // by default only show it if touch device

 			if (options.touchonly === true && !("ontouchend" in document))
 				return $(this);

 			return $(this).each(function (i, obj) {
 				var $t = $(this),
 				    o = $.extend({}, {
		 				    	where: 'after',
		 				    	step: $t.slider('option', 'step'),
		 				    	upIcon: 'ui-icon-plus',
		 				    	downIcon: 'ui-icon-minus',
		 				    	text: false,
		 				    	upText: '+',
		 				    	downText: '-',
		 				    	buttonset: true,
		 				    	buttonsetTag: 'span',
		 				    	speed: 150
	 				    }, options),
 				    $buttons = $('<' + o.buttonsetTag + ' class="ui-slider-access">' +
 				    	'<button data-icon="' + o.downIcon + '" data-step="-' + o.step + '">' + o.downText + '</button>' +
 				    	'<button data-icon="' + o.upIcon + '" data-step="' + o.step + '">' + o.upText + '</button>' +
 				    	'</' + o.buttonsetTag + '>');

 				$buttons.children('button').each(function (j, jobj) {
 					var $jt = $(this),
 						timeout = null,
 						increment = function($jt, $t, e) {
						 		var step = $jt.data('step'),
									curr = $t.slider('value'),
									newval = curr += step * 1,
									minval = $t.slider('option', 'min'),
									maxval = $t.slider('option', 'max');
								e.preventDefault();
								if (newval < minval || newval > maxval)
									return;
								$t.slider('value', newval);
								$t.slider("option", "slide").call($t, null, { value: newval });
						 	};
 						
 					$jt.button({
 						text: o.text,
 						icons: { primary: $jt.data('icon') }
 					})
 					.bind('touchstart mousedown', function (e) {
 						increment($jt, $t, e);
 						timeout = setInterval(function () {
 							increment($jt, $t, e);
 						}, o.speed);
 					});

 					$(document).bind('touchend mouseup', function () {
	 						clearInterval(timeout);
	 						return false;
	 					});

 				});

 				// before or after
 				$t[o.where]($buttons);

 				if (o.buttonset) {
 					$buttons.removeClass('ui-corner-right').removeClass('ui-corner-left').buttonset();
 					$buttons.eq(0).addClass('ui-corner-left');
 					$buttons.eq(1).addClass('ui-corner-right');
 				}

 				// adjust the width so we don't break the original layout
 				var bOuterWidth = $buttons.css({
 					marginLeft: (o.where == 'after' ? 10 : 0),
 					marginRight: (o.where == 'before' ? 10 : 0)
 				}).outerWidth(true) + 5;
 				var tOuterWidth = $t.outerWidth(true);
 				$t.css('display', 'inline-block').width(tOuterWidth - bOuterWidth);
 			});
 		}
 	});

 })(jQuery);

A quick look at the source and you will notice I also threw in a few options to make things more versital. The options are:

  • touchonly – true/false – Show this control Only for touch devices.
  • where – before/after – Show this control before or after the slider.
  • step – [0-9]+ – How much each button press increments. Defaults to slider step.
  • upIcon – one of ui icon names – defaults to “ui-icon-plus”.
  • downIcon – one of ui icon names – defaults to “ui-icon-minus”.
  • text – true/false – Show text in buttons?
  • upText – string – Text of up button, default is “+”.
  • downText – string – Text of down button, default is “-“.
  • buttonset – true – Wrap the button in a button set?
  • buttonsetTag – tagName – element type to wrap the buttons with, default is “span”.
  • speed – [0-9]+ – Interval speed on touch-hold or click-hold to increment, default is 150

It should also be noted that for this plugin to work you will need to include the button widget in your jQueryUI build. From there we handle the rest. When a button is clicked the change event will be fired through the slider, however the slide event will not. This plugin makes no attempt to stop the click event on the buttons, so if you need to catch these events since there is not slide event you may do it the natural way.

One final note for this plugin. It is small, I will likely not be making many updates to it. If you need to tweak something feel free, and if you like post your changes here. I may integrate it if I see the need. The downfall and updside is as time passes jQueryUI will likely improve their support, so this plugin will become irrelevant. Enjoy!

Last updated: 2012-09-23 – SliderAccess Now has a dedicated documentation page and GitHub repository