Switch On The Code RSS Button - Click to Subscribe
Oct
29

Javascript Controls - The Spin Control

Today we have a new little javascript ui control to talk about - although the only thing little about it is its actual size on the page. It is a fully featured spin control, or, as some people call it, a numeric up-down control. We have been doing a number of tutorials on javascript user interface elements, and as you may have noticed, they can get quite long. So we thought it was time to take a different tact - where we package up the component as much as possible and instead of spending so much time on how it works, we will spend more time on how to use it. Don't worry though, we will still go into detail on parts of the code that we think are interesting or complex.

Note: The leftmost spin control controls the height of this box.


And theres your spin control. It is modeled after the standard Windows spin control, so none of the behavior should be surprising. Just by looking at the controls, you probably noticed that they are themeable - you can set the border, background, button, and font color (both using css and in code). You can also set the width, down to a minimum of 25 pixels. Features that might not be immediately noticeable: you can set the minimum, maximum and initial value, and you can also set how much a single up or down click will move the value (the increment amount).


You can also do more complex things with how much you want the control to increment - you can give it a set of acceleration values, very similar to the Windows spin control. So you can say things like "after the button is held down for 5 seconds, increment by 10 at a time, and after the button is held down for 10 seconds, increment by 25 a time" - and you can set as many of those type of rules as you want.

The other cool thing about this control is that instead of a single callback function that gets called when the value changes (like most of our other javascript components), this spin control introduces something new. You can 'attach' and 'detach' fuctions to the 'ValueChanged' event on the spin control, using the attach and detach methods on the spin control object. So, for instance, the leftmost spin control in the example below has two functions attached to its value changed event - one to update the text to the right of the controls, and the other to control the height of the div surrounding the spin controls.

Oh, and by the way, the scroll wheel works too :)

Ok, enough about the features - lets start looking at how to use the features. We are going to start by going over the public functions on the spin control object:

function SpinControl()
{
  this.GetContainer = function()

  this.GetCurrentValue = function()

  this.SetCurrentValue = function(value)

  this.GetMaxValue = function()

  this.SetMaxValue = function(value)

  this.GetMinValue = function()

  this.SetMinValue = function(value)

  this.GetIncrement = function()

  this.SetIncrement = function(value)

  this.GetWidth = function()

  this.SetWidth = function(value)

  this.SetBackgroundColor = function(color)
 
  this.SetButtonColor = function(color)
 
  this.SetFontColor = function(color)
 
  this.SetBorderColor = function(color)

  this.StartListening = function()
   
  this.StopListening = function()
 
  this.AttachValueChangedListener = function(listener)
 
  this.DetachValueChangedListener = function(listener)

  this.GetAccelerationCollection = function()
}

Most of these function names are pretty self-explanatory, but I'll give a short sentence or two on each of them.

GetContainer
This function returns the html element that contains the control - in the case of the spin control, it is a div. This element is what you add to the DOM when you want to add a spin control to the page (we will go over how to do that in a little bit).

Get And Set CurrentValue
These two functions do exactly what you might expect - one returns the current value of the spin control, and the other sets the value of the spin control. By default, this value starts off as 1. If you set a value through here, it is still constrained by the max/min set on the control, so if you have a max set of 10 and you try to set the value of the spin control to 20, the value will actually be set to 10.

Get And Set MaxValue
These two functions let you get and set the maximum value for the spin control. By default, the maximum value is 100.

Get And Set MinValue
These two functions let you get and set the minimum value for the spin control. By default, the minimum value is 0.

Get And Set Increment
These two functions let you get and set the increment value for the spin control. By default, the increment value is 1. The increment value is always used for the initial movement of the spin control value when the user clicks on the up or down buttons. If there is no acceleration set (we will get into how to set acceleration later), the spin control will continue to modify the current value by the increment if the user holds down the up or down buttons. As you might expect, the up button adds the increment to the current value, and the down button subtracts the increment.

Get And Set Width
These two functions let you get and set the width of the spin control. By default, the minimum value is 50, and if the minimum width is 25 pixels (i.e., if you pass in a value less than 25, the width will be set to 25).

SetBackgroundColor
This function, as you might expect, sets the background color of the spin control. Using this function sets the color for a particular spin control - you can also modify the default background color in the spin control style sheet (which we will take a look at in a little while). The default background color is white.

SetButtonColor
This function lets you set the color of the up and down buttons on the spin control. As with the background color, you can set the color for all spin controls by modifying the style sheet (the default color is black). For IE6 users, sadly, the buttons will always be black, because we use a transparent png background image to accomplish the styling (and there is no way to make IE6 do background image transparency correctly, even using the filter hack).

SetFontColor
Here, you can set the font color. By default, it is black, and you can change the default (as well as the font family and size) in the style sheet.

SetBorderColor
And the last style function, setting the border color. By default, this is a light grey. Just as a side note, the reason that these style functions are exposed (instead of just changing the styles on the html elements directly) is that most of these style functions changes values on multiple elements, and so it is a nice way to encapsulate the necessary changes.

StartListening and StopListening
These functions attach and detach all the needed events for the spin control to work. When the spin control is first created, it is not listening - after you add it to the DOM, you need to call the StartListening to enable the control. The reason it does not listen initially is that until it is added to the DOM, event attaching does not work. Yeah, we know it is an annoyance, and we are trying to find a clean way around it - for now, youll just have to call StartListening after you add the control.

AttachValueChangedListener and DetachValueChangedListener
These two functions attach and detach functions to the ValueChanged event. The argument is the function that you want to attach (or detach). When the event occurs, the attached functions are called with two arguments - the spin control that is sending the argument, and the new value.

GetAccelerationCollection
Now we get to the one actually complicated getter. This function returns a reference to the SpinControlAccelerationCollection for the spin control. What in the world is a SpinControlAccelerationCollection, you ask? Well, it is how you set up accelerations for the spin control. So lets take a look at how to work with this new object:

function SpinControlAccelerationCollection()
{ 
  this.GetCount = function()
 
  this.GetIndex = function(index)
 
  this.RemoveIndex = function(index)
 
  this.Clear = function()
 
  this.Add = function(spinControlAcceleration)
}

These functions just let you interact with the collection, adding and removing acceleration objects (which we will talk about in a moment).

GetCount
This function simply returns the current number of SpinControlAcceleration objects in the collection.

GetIndex
This function returns the SpinControlAcceleration at an index into the collection. If the index is invalid, it returns null.

RemoveIndex
This function removes whatever is at the given index from the collection. If it is an invalid index, nothing happens.

Clear
This just clears all the SpinControlAcceleration out of the collection, so you are left with an empty collection.

Add
And this is how you add a SpinControlAcceleration object to the collection. This function doesn't simple add the object to the end of the collection - the collection is always sorted based on when the acceleration is supposed to occur. So if you add an acceleration that is supposed to occur after 5 seconds, and then you add one that is supposed to occur after 2.5 seconds, the second one will be before the first in the collection. So now lets take a look at the pretty simple SpinControlAcceleration object itself:

function SpinControlAcceleration(increment, milliseconds)
{   
  this.GetIncrement = function()
 
  this.GetMilliseconds = function() 
}

This object just holds two values - an amount to increment, and a time. Essentially, it means that when the given amount of time has passed, start incrementing using the given increment value. Don't worry if this doesn't quite make sense yet - we are about to dive into how to set up an instance of the spin control with some acceleration.

Remember the examples from above? Well, lets take a look at the code to set up those up. First, the leftmost spin control:


First, the initial html code (i.e., the place in the page where the spin control is going to be added):

<div id="spinCtrlContainer"
    style="position:relative;border:1px solid black;
    width:530px;height:40px;"
>

  <div id="printOut" style="position:absolute;left:290px;top:10px;"></div>
</div>

A div with a border, and a div to hold that text that gets printed when the spin control changes. Nothing that special. So now for the javascript:

var spinCtrl = new SpinControl();
spinCtrl.Tag = 'left';
spinCtrl.SetMaxValue(9999);
spinCtrl.SetMinValue(32);
spinCtrl.GetContainer().style.position = 'absolute';
spinCtrl.GetContainer().style.left = '15px';
spinCtrl.GetContainer().style.top = '10px';
spinCtrl.AttachValueChangedListener(spinCtrlPrintOut);
spinCtrl.AttachValueChangedListener(spinCtrlSizeBox);
spinCtrl.SetCurrentValue(40);

This is most of the setup code - as you can see, we are setting the max value, the min value, and some position information on the container. We set a "Tag" on the control just so we can easily identify it later. We attach two functions to the value changed listener, and we will take a look at the code for those in a moment. Finally, we set the initial value.

Thats most of the general setup code - now lets look at the code for setting up the acceleration:

spinCtrl.GetAccelerationCollection().Add(
    new SpinControlAcceleration(1, 500));
spinCtrl.GetAccelerationCollection().Add(
    new SpinControlAcceleration(5, 1750));
spinCtrl.GetAccelerationCollection().Add(
    new SpinControlAcceleration(10, 3500));
spinCtrl.GetAccelerationCollection().Add(
    new SpinControlAcceleration(40, 7000));
spinCtrl.GetAccelerationCollection().Add(
    new SpinControlAcceleration(80, 10000));

What does this mean? Well, say you pressed down on the up arrow. First, it will increment by 1 - the default increment value for the spin control (which we did not change). Then, after 500 milliseconds, it will start incrementing 1 at a time (approximately 6-7 times per second). Then when 1.75 seconds have passed (since you initially pressed the up arrow) it will start incrementing 5 at a time. Then at 3.5 seconds it will switch to 10 at a time, followed by 40 at a time after 7 seconds. And finally, after 10 seconds have passed, it will increment at 80 at a time, and it will continue at that speed until the spin control reaches its max value or you release the up arrow.

The next thing we have to do is actually add the control to the page:

var el = document.getElementById('spinCtrlContainer');
el.appendChild(spinCtrl.GetContainer());
spinCtrl.StartListening();

And finally, we need to define those two functions we attached to the value changed event:

function spinCtrlPrintOut(sender, newVal)
{
  document.getElementById('printOut').innerHTML =
    'The ' + sender.Tag + ' spin control is now ' + newVal +'.';
}

function spinCtrlSizeBox(sender, newVal)
{
  document.getElementById('spinCtrlContainer').style.height =
    newVal + 'px';
}

And there you go! That is all you need to do to get that first example up and happy. Lets take a quick look at the second example, and see how to do the theming:


One way to do the theming (and the way it was done for this example) is in the javascript code:

spinCtrl.SetBackgroundColor('#EDFCC3');
spinCtrl.SetBorderColor('#148238');
spinCtrl.SetButtonColor('#148238');
spinCtrl.SetFontColor('#555533');

The other way to do the theming (and this would apply to all spin controls on the page) is to change the style sheet:

.spinInput, .spinContainer
{
  /* Change this to modify the default
   * spin control background color*/

  background-color: #FFFFFF;
}

.spinLeftRightEdge, .spinTopBottomEdge
{
  position: absolute;
  overflow: hidden;
  /* Change this to modify the default
   * spin control border color*/

  background-color: #A5ACB2;
}

.spinInput
{
  position: absolute;
  top: 1px;
  left: 2px;
  height: 18px;
  border: 0px;
  /* Change these to modify the default spin
   * control font, font color, and font size*/

  color: Black;
  font-size: 9pt;
  font-family: Arial;
}

.spinUpBtn, .spinUpBtnHover, .spinUpBtnPress,
.spinDownBtn, .spinDownBtnHover, .spinDownBtnPress
{
  position: absolute;
  width: 15px;
  height: 8px;
  right: 2px;
  background-image: url('spin_control_buttons.png');
  background-repeat: no-repeat;
  /* Change this to modify the default button color*/
  background-color: #000000;
}

There is a bunch more css in the actual css file for the spin control (of course :P), but what is shown above is the important stuff for theming. Hey, I even threw in some comments!

And that is all there is to it. Here is the source code download link again, which is a zip file that includes the javascript, the css, the button images, and a small example html page. If you have any questions on how to use the control, or any questions on the inner workings, please leave a comment.



Posted in Javascript, All Tutorials by The Tallest |

14 Responses

  1. Eliza Says:

    Awesome scroll-wheel action!

  2. Parvaneh Says:

    Amazing SpinCtrl.Please add the keyup event hook for _textbox to the StartListening function.

    hookEvent(_textBox, ‘keyup’, BoxChange);

    in this way when user changes the text box the spin buttons are aware of that and also user can not enter characters other than digits.

  3. Rohi Says:

    It’s really awesome.

    Suggestion: It would be much appreciated if you combine the keyboard event like up arrow and down arrow event with the spin control, without which it is only partial.

    Do update!

  4. Mark Beaty Says:

    Hey, this is a really nice component. I’ve modified it slightly to allow specification of the id attribute value since I want to be able to submit multiple spinner controls via form submittal.

  5. Mark Beaty Says:

    Looks like my markup didn’t come through. Again…

    I’ve modified the javascript slightly to allow specification of the input tag id attribute value since I want to be able to submit multiple spinner controls via a form. Hope that makes sense.

  6. Ed Says:

    Neat control. However, I found that if you add the DTD to example.html in the source that you can download,

    you’ll lose the bottom line of the spin control. Is there a way to fix that?

  7. Abubakar Ibrahim Says:

    your site is great. it’s a centre for alot of resource!

  8. Mshadows Says:

    how set or get a value on a control that i create??
    for example;

    I create a control with tag=’Control1′
    and i want to change is value with a function in a JS..

    please help

  9. jerone Says:

    This is a great script, but I do like to know what browser this is tested and known to work on. There’s also no license specified.
    Adding the mouse up/down suggestion would totally finish it.

    gr J

  10. The Reddest Says:

    We usually test the Javascript code under all the major browsers - IE7, FF, Opera, and Safari. All of our code is licensed under BSD, we just haven’t gotten around to stating that anywhere.

  11. Scott Says:

    I love the control, and want to know if I can override the click events of the up and down buttons to use my own methods. I have some AJAX methods that query various web services, and they return the “Next” and “Previous” available value for the control. I’d like to be able to use your spin control to layout the textbox and buttons, but use my own methods to calculate the highest or lowest value to display in the textbox.

    Is this possible?

  12. Anonymous Says:

    Tks & rgds. that code is greate!

  13. Anonymous Says:

    Very nice, thank you!

  14. Sufiyan Says:

    Is there any update on Rohi’s comment. I also need a same kind of functionality (ie. Keyboard up and down arrow keys event’s integration with SpinControl). I need it urgently.

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.

Powered by WP Hashcash