This class lives in a folder called components. The first thing to notice is the package declaration - ca.xty.components. This tells us that in a folder called ca, there is another folder called xty, and in that folder is a folder called components. This is a piece of my class library, and if you don't already have a library of your own, you should seriously consider building one. We use other classes in the XSlider class such as ShapeFactoryComp and SliderBubble, but we don't have to import these classes to use them since they all live in the same folder, components. We do need to import the classes within the folders display, events and text. Using the asterisk allows us to import all the classes in these folders.

package ca.xty.components {
    import flash.display.*;
    import flash.events.*;
    import flash.text.*;


Next we declare the public and private variables we will use within the XSlider class.

public class XSlider extends MovieClip {
        //This is the variable we use in the Event we use to tell our program the slider is ready
        public static const SLIDER_READY:String = "SLIDER_READY";
       
        //The following public variables are used to customize the look and actions of the slider. They all have default values so that you are not required to explicitly set any of them.
        public var passNum:Number = 0;// This is the variable used to pass on the result obtained from the slider
        public var trackColor:Array = [0xf0f0f0, 0x7b7b7b];// This sets the track color. The drawing application requires this to be a gradient so we need a two number array.
        public var iconColor:Array = [0xf0f0f0, 0x7b7b7b];// This sets the gradient color of the triangle icon
        public var strokeColor:Number = 0x989898;// This is the stroke - line - color. It is used in building the scale, the track and the triangle icon
               
        public var iconDiameter:int = 10;// This is the diameter of the triangle icon
        public var trackWidth:int = 100;// This is the track width
        public var trackHeight:int = 2;// This is the track height - remember there is also a 1 pixel line drawn
        public var strokeWidth:int = 1;// This is the stroke width and is used in the drawing of the scale, track and triangle icon
       
        public var minNum:Number = 0;// The minimum number used in establishing the overall range of the slider
        public var maxNum:Number = 100;// The maximum number
        public var numDivisions:int = 5;// This sets up how many divisions the scale is divided into
       
        public var useScale:Boolean = true;// This sets whether or not the scale is used
       
        public var scaleMarker:Array = new Array();// This array creates a custom scale
       
        public var useSliderBubble:Boolean = false;// This sets whether we use the SliderBubble or not
       
        public var constantUpdate:Boolean = false;// This determines how often we dispatch the SLIDER_READY event. A setting of false dispatches the event only when the icon has stopped moving
       
        //The private variables below are the internal variables for use within this class only.
        private var useLocalScale:Boolean = false;
        //SliderBubble is a class which produces the bubble we use to indicate the number the user is over.
        private var sb:SliderBubble;
        private var sbOn:Boolean = false;
        private var numAt:Number = 0;
        private var posMultiplier:Number;
        private var divNum:int = 0;
       
        private var xFactor:int;
        private var markerX:int;
        private var markerY:int;
        private var valueFactor:Number;
       
        private var scaleTxt:Array = new Array();
        private var scaleMarkerX:int;
        private var scaleMarkerY:int;
        private var scaleFormat:TextFormat;
       
        private var triangleIcon:Sprite;
        private var track:Sprite;
        private var scale:Sprite;
        //The ShapeFactoryComp class provides the graphics for our slider.
        private var sf:ShapeFactoryComp = new ShapeFactoryComp();
       
        //These are the variables we use in our loops.
        private var i:uint;
        private var len:uint;


First of all we declare our public variables. The public designation means that these variables are accessible outside of the XSlider class. This enables us to set various properties such as trackColor and iconColor and therefore we can customize the slider we create. The variable passNum is the variable we will use to pass the result to the Main.as class. Because it is public, passNum can be picked up as we have seen in the Main class by referencing the instance of the XSlider class like so: xs1.passNum. The public static const SLIDER_READY variable is a reference we use when we dispatch the event that tells our Main class that the variable passNum is ready to be picked up. The comments explain the use of each of the public variables.

The private variables are the bits and pieces we will use only inside the XSlider class.

The Constructor:

public function XSlider() {
    //Once the slider instance has been added to the stage using addChild we proceed with building the slider via the init function.
    addEventListener(Event.ADDED_TO_STAGE, init);
}


Until I started building these components I never fully appreciated the event ADDED_TO_STAGE. What this means for us is that we can instantiate the XSlider, and because it now exists we can set it's public properties before we set about physically creating it. The other way to do this is to pass the properties to the XSlider class via the constructor, but just imagine having to type in all of them every time you create a new slider. This way, because they all have default values, we don't have to type anything if we want to use it straight out of the box. So, we add the event listener which will run the init function once our instance of the XSlider has been physically added to the stage using the addChild() method.

private function init(e:Event = null):void{
    //First thing we do is remove the event listener that got us here.
    removeEventListener(Event.ADDED_TO_STAGE, init);
           
    //Here we set up out TextFormat
    scaleFormat = new TextFormat();
    scaleFormat.size = 8;
    scaleFormat.color = 0x000000;
    scaleFormat.font = "Verdana";
    scaleFormat.align = "center";
           
    //If our scaleMarker array has length, we set our variable numDivisions to equal the scaleMarker length minus 1.
    if(scaleMarker.length){
        numDivisions = scaleMarker.length - 1;
    }
    //Our xFactor variable is calculated to equal our track width divided by our numDivisions
    xFactor = trackWidth / numDivisions;


The first job in the init function is to remove the ADDED_TO_STAGE event listener. Then we set the properties of our TextFormat, scaleFormat. Now we check to see if the scaleArray has length. When we declared this variable we created an empty array, so unless we have passed in a custom array, then the length will be 0. If it does have length, then we set our numDivisions variable to equal the scaleMarker array's length minus one. Why minus one? If we have 5 divisions then we have six lines. The scaleMarker array holds the information to build those lines, so we will want our numDivisions to always be one less than the length of our scaleMarker array. Next we give our xFactor variable the value of trackWidth divided by numDivisions. If we are using the default settings, this would equal 20: trackWidth (100) divided by numDivisions (5). This tells us the number of pixels between each marker, something we will make use of shortly.

Now we'll set up our scale - or not.

//If useScale is set to true - meaning we want to show a scale on stage - we first check to see if the scaleMarker array contains anything.
if(useScale){
    //If the scaleMarker array does not contain anything, we put together a standard array to use.
    if(!scaleMarker.length){
        //We set our variable useLocalScale to true.
        useLocalScale = true;
        divNum = maxNum / numDivisions;
        //Our length for the loop we are about to do is set to numDivisions plus 1 because we want one more line than the number of divisions.
        len = numDivisions + 1;
        //The variable t is a String representation of the scale number, and the variable sn is the integer version.
        var t:String = String(minNum);
        var sn:Number = minNum;
        for (i = 0; i < len; i++){
            var sObj:Object = {Txt:t, data:sn};
            scaleMarker.push(sObj);
            sn += divNum;
            //sn += xFactor;
            t = String(sn);
        }
    }
    // scale is the Sprite that will hold all the bits that make up our scale graphic.
               
    scale = new Sprite();
    scale.graphics.lineStyle(1, strokeColor, 1);
    markerX = 0;
    len = numDivisions + 1;
    for (i = 0; i < len; i++){
        scale.graphics.moveTo(markerX, -12);
        scale.graphics.lineTo(markerX, 0);
        markerX += xFactor;
    }
               
    //Next we put the TextFields in place. I am making them 20 pixels wide by 16 pixels high. I want them to be centered over the lines we just drew, so we'll start the scaleMarkerX
    // variable at -10. We want them to sit just on top of the lines. The top of the lines are at -12 and the TextFields are 16 pixels high so we set the scaleMarkerY at -28.
    scaleMarkerX = -10;
    scaleMarkerY = -28;
    len = scaleMarker.length;
    for (i = 0; i < len; i++){
        scaleTxt[i] = new TextField();
        scaleTxt[i].x = scaleMarkerX;
        scaleTxt[i].y = scaleMarkerY;
        scaleTxt[i].width = 20;
        scaleTxt[i].height = 16;
        scaleTxt[i].autoSize = TextFieldAutoSize.CENTER;
        scaleTxt[i].text = scaleMarker[i].Txt;
        scaleTxt[i].setTextFormat(scaleFormat);
        scale.addChild(scaleTxt[i]);
                   
        scaleMarkerX += xFactor;
    }
               
    addChild(scale);
}else{
    //If we are not using our scale graphic we want to set our posMultiplier variable. This variable will help us determine the number the user is currently over.
    //If minNum is equal to 0 we simply divide our maxNum by the track width. If minNum is not equal to 0, we subtract the minNum from the maxNum and then divide it by the track width.
    if(minNum == 0){
        posMultiplier = maxNum / trackWidth;
    }else{
        posMultiplier = (maxNum - minNum) / trackWidth;
    }
}

The first thing to determine is whether useScale is true or false. If it is true, then we next need to check and see if we are using a custom scale buy seeing if the scaleMarker array has length. If it does not have length, then we will be using a locally generated scale, so we set our useLocalScale variable to true. Now we need to determine our variable divNum in case we are using a custom min/max number. To calculate this we divide our maxNum by the numDivisions. Then we set the len variable to numDivisions plus one in order to determine how many lines we will need to draw. Remember, the number of lines will always be one more than the number of divisions. Now we set up to local vars - t as a String and sn as a Number, and initially set them to our minNum variable. The t variable will be what we use to populate the TextField and the sn variable will be what use to make our calculations. With all of these things figured out we run our for loop. We create an Object with two properties - Txt for the label, and data for the number and assign them our t and sn variables. Then we push this Object into our scaleMarker array. Once that is done, we add the value of divNum to the sn variable and then set t to equal the String equivalent of sn.

Then we move on and create the lines for our scale. First we create a new Sprite called scale, and then assign a lineStyle to it's graphics property. The lineStyle has three properties: first is the line thickness, then the line color and finally the line alpha. We want these lines to be 1 pixel wide, set to our strokeColor variable, and with an alpha of 1. To get started we set our markerX variable to 0 and our len variable to numDivisions plus one. In the for loop we start by using the moveTo function to determine the starting x and y properties. The x property will be set by using our markerX variable. Each line will be 12 pixels high so our y property is set to -12. In the next line we draw the line using the lineTo function. This line is straight up and down, so the x property remains constant, but the y property is set to 0 which will intersect with our track. To prepare for the next line to be drawn we add our xFactor (the number of pixels between divisions) to our markerX and loop back to the beginning.

With our lines in place, we now construct the TextFields. Our default setting for the trackWidth is 100, and our default setting for the numDivisions is 5, which gives us an xFactor of 20. With this in mind I am creating each TextField to be 20 pixels wide, with a height of 16 pixels. In order to center the TextFields over each line we set our initial scaleMarkerX variable to be -10. The scaleMarkerY variable is set to -28 because the lines are 12 pixels high and the TextFields are 16 pixels high: 12 + 16 = 28, and since they are going to be above our track, which has a y position of 0, we will need to make that 28 a minus 28. We use our array scaleTxt to hold each TextField, and the reason we use an array at all is because we never know how many TextFields we will be needing, and this method allows us to have as many as we need. The first line in our for loop creates a new TextField and assigns it to the i index of the scaleTxt array. Then we assign the x and y properties, and the width and height properties. Then, in case we require a TextField wider than 20 we set the autoSize property to scale the TextField using a value of CENTER. This will expand the TextField equally to the left and right thereby keeping it centered over of line. Next we assign the text property using the Txt property at i index of our scaleMarker array, and set the TextFormat to our scaleFormat variable. Now we add it as a child of our scale Sprite. With all that done, we update the scaleMarkerX property by adding our xFactor to it. When the for loop finishes running, we use addChild(scale) to place the whole thing on stage.

But, if useScale is false then we need to set our variable posMultiplier so that we can properly calculate the number we want to send back to our Main class. The posMultiplier is used to match our scale. What we are measuring is the x position of the triangle icon. Using our default settings - minNum:0, maxNum:100, and trackWidth:100, our posMultiplier would be 1 - maxNum divided by trackWidth. However, if our maxNum was 200 then we would have a posMultiplier of 2, or if our maxNum was 100, but our trackWidth was 200 then we would have a posMultiplier of .5. The first thing we need to determine is if our minNum is equal to zero. If it is then we can simply divide our maxNum by our trackWidth. If minNum is not equal to zero, then we need to first subtract our minNum from our maxNum and then divide the result by our trackWidth. For example, if our minNum was 100 and our maxNum was 500 then our scale range is 400. Dividing this by our trackWidth of 100, our posMultiplier would be 4. Scratching your head because if we simply divide the maxNum (500) by the trackWidth (100) then on the far right of the slider would be 500, right? Right, but what we have to consider here is the minNum (100). On the far left instead of having 0, we need to show 100. As you'll soon see, it will all work out.

Last, but not least we need to build our track and triangle icon.

track = sf.createHorzGradWithLine(trackColor, strokeColor, strokeWidth, trackWidth, trackHeight);
track.x = 0;
track.y = 0;
addChild(track);
//And here we build our triangle icon. We set up the MOUSE_DOWN and MOUSE_UP event listeners so we can start and stop the drag feature.
triangleIcon = sf.createSliderTriangle(iconColor, strokeColor, iconDiameter );
triangleIcon.x = 0;
triangleIcon.y = 0;
triangleIcon.buttonMode = true;
triangleIcon.useHandCursor = true;
triangleIcon.addEventListener(MouseEvent.MOUSE_DOWN, onStartMove);
triangleIcon.addEventListener(MouseEvent.MOUSE_UP, onStopMove);
addChild(triangleIcon);


We start by building our track. Through our ShapeFactoryComp class (which we will look at in a minute) we use the function createHorzGradWithLine. The properties this function takes are trackColor, strokeColor, strokeWidth, trackWidth, and trackHeight. We set the x and y properties to 0 and use addChild to put it on stage.

Next we build our triangle icon using the ShapeFactoryComp class's createSliderTriangle function whose properties are iconColor, strokeColor, and iconDiameter. We also set the x and y properties to 0. In order to use a hand cursor icon we set the buttonMode property to true and then the useHandCursor property to true as well. The triangle icon runs the whole show, so we add two event listeners, MOUSE_DOWN and MOUSE_UP, which will control the drag functions.

Now that all the bits are in place, let's look at the functions which bring the whole thing to life.

private function onStartMove(event:MouseEvent):void{
    triangleIcon.startDrag();
    addEventListener(Event.ENTER_FRAME, monitorMove);
    stage.addEventListener(MouseEvent.MOUSE_UP, onStopMove);
}


Our MOUSE_DOWN event triggers the onStartMove function, where we declare the startDrag() function and attach it to the triangleIcon. Now we add two more event listeners, ENTER_FRAME, and a MOUSE_UP event attached to the stage instance. The ENTER_FRAME event will constantly monitor our triangleIcon's movements. The second MOUSE_UP event is added to the stage so that even if your mouse is no longer over the triangle icon, we will still be able to stop the dragging process.

private function monitorMove(e:Event):void {
    if(triangleIcon.y != 0){
        triangleIcon.y = 0;
    }
   
    if(triangleIcon.x < 0){
        triangleIcon.x = 0;
    }
   
    if(triangleIcon.x > trackWidth){
        triangleIcon.x = trackWidth;
    }
   
    numAt = triangleIcon.x;
           
    if(useScale){
        if(useLocalScale){
            valueFactor = scaleMarker[1].data / xFactor
            passNum = numAt * valueFactor;
        }else{
            len = scaleMarker.length;
            for (i = 0; i < len; i++){
                if(numAt <= xFactor){
                    valueFactor = scaleMarker[i + 1].data / xFactor;
                    passNum = numAt * valueFactor;
                    break;
                }else if(numAt > xFactor && numAt <= xFactor * i){
                    valueFactor = (scaleMarker[i].data - scaleMarker[i-1].data) / xFactor;
                    passNum = ((numAt - (xFactor * (i-1))) * valueFactor) + scaleMarker[i-1].data;
                    break;
                }
            }
        }
    }else{
        if(minNum == 0){
            passNum = numAt * posMultiplier;
        }else{
            passNum = (numAt * posMultiplier) + minNum;
        }
    }
   
    if(useSliderBubble){
        if(!sbOn){
            sb = new SliderBubble(0xffffff, 0xff0000, 50, String(passNum.toFixed(2)));
            sb.x = mouseX - (sb.width / 2);
            sb.y = mouseY - 40;
            addChild(sb);
            sbOn = true;
        }else{
            sb.updateSliderTxt(String(passNum.toFixed(2)));
            sb.x = mouseX - (sb.width / 2);
        }
    }
   
    if(constantUpdate){
        dispatchEvent(new Event(XSlider.SLIDER_READY, true));
    }
}


The first order of business is to make sure our triangle icon is behaving. We want to keep the y position of the icon on 0, so if it happens to wander off, we stick it right back. Next we look at the x position of the triangle icon. Here we check to see if the icon's x position is less than 0 and make it equal to 0 if it is. And, going the other way, to keep the icon from running off the end of the track we check to see if it's x position is greater than the track width and set it to equal the track width if it is.

Next, we set our variable numAt to record the x position of the triangle.

Our next job is to figure out the number we need to pass back to the main application, and set our passNum variable equal to that. We have a few different scenarios to consider here. If useScale is true, then we will set our variable called valueFactor. And, if we are using our scale graphic, then we need to check and see if useLocalScale is true. If useLocalScale is true, our calculation is fairly simple. We take the scaleMarker array's second data property [1] and divide it by xFactor to determine the valueFactor. Since this is our own locally made scaleMarker array we can be certain that the values between markers will be consistent. Once we have figured out the correct valueFactor, we determine our passNum by multiplying our numAt times valueFactor.

If we are not using local scale, we are therefore using a custom scaleMarker array, and we calculate the valueFactor in a slightly different way. Since we cannot be sure of the difference between markers we first need to determine where we are. First we check to see if numAt is less than or equal to our xFactor. If it is then we know we are between the first marker and the second marker so we can get the data property from the second object in the scaleMarker array by adding i plus 1, which in this case is really 0 plus 1, then we divide that value by our xFactor. Now we calculate our passNum by multiplying the numAt times the valueFactor. Now that we have a result, we then break out of the loop.

If our numAt is not less than or equal to xFactor, then we need to look further ahead. We check now to see if numAt is greater than xFactor and less than or equal to xFactor times i. Once we have found our correct range, our valueFactor is calculated by dividing the result of the current scaleMarker object's data property (i) - the previous scaleMarker object's data property (determined by i - 1) by our xFactor. Once we determine the valueFactor value we proceed to calculate our passNum. First we subtract our xFactor times i minus 1 from numAt, multiply that times our valueFactor and add the previous scaleMarker object's data property. Let's do a quick example using our custom scaleMarker array. Let's say that the triangleIcon is halfway (30 pixels) between the second and third markers, that is between 100 and 1000. Our valueFactor is calculated like this: (1000 - 100) / 20 which equals 45. Our passNum is then calculated like so: (30 - (20 * 1) * 45) + 100 which equals 550. Now that we have a result, we break out of the loop.

If we are not using scale, then we need to determine what our minNum is. If it is 0, the default, then our passNum is simply the numAt times the posMultiplier. If minNum does not equal 0 then we have a custom set of parameters, so we take the result of the numAt times the posMultiplier and add the minNum to it. For example: if your custom range is from 100 - 200 and you are in the very middle you want the number returned to be 150. So, numAt(50) times posMultiplier(1) plus the minNum (100) equals 150.

Now that we have our result we need to check if useSliderBubble is true. If it is, then we create the bubble and pass it the passNum variable we just figured out above cast as a String. We need to also see if the sliderBubble is already showing, and if it is, then we only want to update the number, not create another bubble. In order to avoid passing numbers like 1.9988762534, we apply the method toFixed(2) to the passNum. This will give us a maximum of 2 numbers after the decimal point. Note that passNum is not affected here, only the version which we are showing in the TextField.

The last thing to check in this function is whether constantUpdate is true or false. If the constantUpdate variable is true we fire off an event to the main application telling it to pick up the public variable passNum.

private function onStopMove(event:MouseEvent):void{
    stopDrag();
    removeEventListener(Event.ENTER_FRAME, monitorMove);
    stage.removeEventListener(MouseEvent.MOUSE_UP, onStopMove);
    if(useSliderBubble){
        removeChild(sb);
        sbOn = false;
    }
    if(!constantUpdate){
        dispatchEvent(new Event(XSlider.SLIDER_READY, true));
    }
}


The final function in this class deals with stopping the drag on the triangleIcon. After declaring stopDrag() we remove the ENTER_FRAME and MOUSE_UP event listeners. If useSliderBubble is true, we also remove it from the stage and set the Boolean variable sbOn to false. And, finally, if constantUpdate is not true, then we dispatch our event to tell the Main class it's time to pick the passNum variable.

The XSlider class makes use of a couple of other classes within the components folder, ShapeFactoryComp and SliderBubble. We'll take a look at those next.