The VerticalScrollbar class, continued

Source File: 

Code listings for the class are going to start getting very long, but much of it is just lists: lists of imports, lists of variables. But here you can see that I've begun bringing in the timeline code and adapting it:

package com.jessamin.controls {
	import flash.display.DisplayObject;
	import flash.display.InteractiveObject;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.EventDispatcher;
	import flash.events.MouseEvent;
	
	public class VerticalScrollbar extends EventDispatcher {
		private var stage:Stage;
		private var thumb:InteractiveObject;
		private var track:DisplayObject;
		private var window:DisplayObject;
		private var content:DisplayObject;
		private var up:InteractiveObject;
		private var down:InteractiveObject;
		
		private var yOffset:Number;
		private var topLimit:Number;
		private var thumbRange:Number;
		private var bottomLimit:Number;
		private var scrollPercent:Number;
		private var contentRange:Number;
		private var speed:Number
		
		public function VerticalScrollbar(  stage:Stage, 
											thumb:InteractiveObject, 
											track:InteractiveObject, 
											window:DisplayObject = null,
											content:DisplayObject = null,
											up:InteractiveObject = null, 
											down:InteractiveObject = null  ) {
			this.stage    = stage;
			this.thumb    = thumb;
			this.track    = track;
			this.window   = window;
			this.content  = content;
			this.up       = up;
			this.down     = down;
			
			yOffset = 0;
            topLimit = this.track.y;
			thumbRange = track.height - thumb.height;
			bottomLimit = track.y + thumbRange;
			if(content && window) contentRange = content.height - window.height;
			scrollPercent = 0;
			speed = 0.01;
			
			thumb.addEventListener(MouseEvent.MOUSE_DOWN, thumb_onMouseDown);
		}
		private function thumb_onMouseDown(event:MouseEvent):void {
			yOffset = thumb.parent.mouseY - thumb.y;
			stage.addEventListener(MouseEvent.MOUSE_MOVE, stage_onMouseMove);
			stage.addEventListener(MouseEvent.MOUSE_UP, stage_onMouseUp);
		}
		private function stage_onMouseMove(event:MouseEvent):void {
			thumb.y = thumb.parent.mouseY - yOffset;
			//restrict the movement of the thumb:
			if(thumb.y < topLimit) {
				thumb.y = topLimit;
			}
			if(thumb.y > bottomLimit) {
				thumb.y = bottomLimit;
			}
			//calculate scrollPercent and make it do stuff:
			scrollPercent = (thumb.y - track.y) / thumbRange;
			if(content && window) content.y = window.y - (scrollPercent * contentRange);
			event.updateAfterEvent();
		}
		private function stage_onMouseUp(event:MouseEvent):void {
			stage.removeEventListener(MouseEvent.MOUSE_MOVE, stage_onMouseMove);
			stage.removeEventListener(MouseEvent.MOUSE_UP, stage_onMouseUp);
		}
		
	}
}

When the timeline code is brought in, the variables should be declared in the variables list, but they should get their initial values in the constructor function. So you have to separate those in each and every case (lines 18-24, and 42-48). It's a lot of work, but it's all just editing. Also, you can add the private modifier to the declarations, and also to the functions. One other change I had to make is that the mouseY reference is no longer available, this class not being a display object container. So that was changed to thumb.parent.mouseY (lines 52 and 57). The thumb's parent (whatever it is) has the mouseY property we're after.

In lines 45 and 67, we have to allow for the fact that either the content variable or the window variable might be null (the user of our class didn't supply a value). So we only execute those lines if they are not null.

Line 45 could have been written in three lines like this:

if(content != null && window != null) {
	contentRange = content.height - window.height;
}

But, guess what? We can shorten that to one neat line. In AS3, any if statements with a one line statement "block" can be written on one line, without the curly braces, instead! And testing for null can be done just by testing the object itself for true or false. So the above was shortened to:

if(content && window) contentRange = content.height - window.height;

Either way gets the same job done, the short form is just less verbose and saves some space. That line says if both objects are not null, do the following, otherwise the whole deal is off! Of course, nobody would send our class one reference without the other, now would they? Well, it doesn't make sense, but you want your code to even allow for edge cases like nonsensical arguments!

The changes so far are enough to make the scrollbar function. One other thing to note: We are going to leave it to the outside code (in the fla) to set the buttonMode for any Sprites. Also, it will be left to the fla file to create the masking.

The reason in the first case (buttonMode) is because the user of the class might not want buttonMode turned on at all. They might prefer the arrow cursor instead. It's less work for us, too, because this way the class need not detect if it's being sent a Sprite, a SimpleButton, or what.

The reason in the second case (the masking) is to allow for the possibility that the user of the class might be using the timeline to do manual masking of one layer by another, and wouldn't in that case want the class making the mask for them. So add these lines to the fla file:

thumb.buttonMode = true;
up.buttonMode = true;
down.buttonMode = true;
content.mask = window;

You can see that we are able to decide which things the class should be responsible for, but if we want the fla file to take care of it instead, well, both code blocks have references to the same set of objects and can tell them what to do. Cool! Now test the movie, and observe that the thumb and track are working and scrolling the content as before.

Next, I continued to add in the timeline code, as before. I didn't experience any other issues, and when I tested, the code worked just like it did when it was on the timeline. Here's the entire class listing so far:

package com.jessamin.controls {
	import flash.display.DisplayObject;
	import flash.display.InteractiveObject;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.EventDispatcher;
	import flash.events.MouseEvent;
	import flash.events.Event;
	
	public class VerticalScrollbar extends EventDispatcher {
		private var stage:Stage;
		private var thumb:InteractiveObject;
		private var track:DisplayObject;
		private var window:DisplayObject;
		private var content:DisplayObject;
		private var up:InteractiveObject;
		private var down:InteractiveObject;
		
		private var yOffset:Number;
		private var topLimit:Number;
		private var thumbRange:Number;
		private var bottomLimit:Number;
		private var scrollPercent:Number;
		private var contentRange:Number;
		private var speed:Number
		
		public function VerticalScrollbar(  stage:Stage, 
											thumb:InteractiveObject, 
											track:InteractiveObject, 
											window:DisplayObject = null,
											content:DisplayObject = null,
											up:InteractiveObject = null, 
											down:InteractiveObject = null  ) {
			this.stage    = stage;
			this.thumb    = thumb;
			this.track    = track;
			this.window   = window;
			this.content  = content;
			this.up       = up;
			this.down     = down;
			
			yOffset = 0;			
			topLimit = this.track.y;
			thumbRange = track.height - thumb.height;
			bottomLimit = track.y + thumbRange;
			if(content && window) contentRange = content.height - window.height;
			scrollPercent = 0;
			speed = 0.01;
			
			thumb.addEventListener(MouseEvent.MOUSE_DOWN, thumb_onMouseDown);
			if(down) down.addEventListener(MouseEvent.MOUSE_DOWN, down_onMouseDown);
			if(up) up.addEventListener(MouseEvent.MOUSE_DOWN, up_onMouseDown);
		}
		private function thumb_onMouseDown(event:MouseEvent):void {
			yOffset = thumb.parent.mouseY - thumb.y;
			stage.addEventListener(MouseEvent.MOUSE_MOVE, stage_onMouseMove);
			stage.addEventListener(MouseEvent.MOUSE_UP, stage_onMouseUp);
		}
		private function stage_onMouseMove(event:MouseEvent):void {
			thumb.y = thumb.parent.mouseY - yOffset;
			//restrict the movement of the thumb:
			if (thumb.y < topLimit) thumb.y = topLimit;
			if (thumb.y > bottomLimit) thumb.y = bottomLimit;
			//calculate scrollPercent and make it do stuff:
			scrollPercent = (thumb.y - track.y) / thumbRange;
			if(content && window) content.y = window.y - (scrollPercent * contentRange);
			event.updateAfterEvent();
		}
		private function stage_onMouseUp(event:MouseEvent):void {
			stage.removeEventListener(MouseEvent.MOUSE_MOVE, stage_onMouseMove);
			stage.removeEventListener(MouseEvent.MOUSE_UP, stage_onMouseUp);
		}
		private function down_onMouseDown(event:MouseEvent):void {
			stage.addEventListener(Event.ENTER_FRAME, scrollDown);
			stage.addEventListener(MouseEvent.MOUSE_UP, stopScrollingDown);
		}
		private function scrollDown(event:Event):void {
			scrollPercent += speed;
			if(scrollPercent > 1) scrollPercent = 1;
			if(content && window) content.y = window.y - (scrollPercent * contentRange);
			thumb.y = scrollPercent * thumbRange + track.y;
		}
		private function stopScrollingDown(event:MouseEvent):void {
			stage.removeEventListener(Event.ENTER_FRAME, scrollDown);
			stage.removeEventListener(MouseEvent.MOUSE_UP, stopScrollingDown);
		}
		private function up_onMouseDown(event:MouseEvent):void {
			stage.addEventListener(Event.ENTER_FRAME, scrollUp)
			stage.addEventListener(MouseEvent.MOUSE_UP, stopScrollingUp);
		}
		private function scrollUp(event:Event):void {
			scrollPercent -= speed;
			if(scrollPercent < 0)scrollPercent = 0;
			if(content && window) content.y = window.y - (scrollPercent * contentRange);
			thumb.y = scrollPercent * thumbRange + track.y;
		}
		private function stopScrollingUp(event:MouseEvent):void {
			stage.removeEventListener(Event.ENTER_FRAME, scrollUp)
			stage.removeEventListener(MouseEvent.MOUSE_UP, stopScrollingUp);
		}
	}
}

I also shortened some of the other if statements to the single line form (lines 61, 62, 78, and 92 above). Next, I practiced sending the class various arrangements of arguments, and found that it worked perfectly in all the different variations I tried. If I left off either of the up and down buttons, then that button was deactivated, but there were no errors. If I left off either window or content, then the content wouldn't scroll, but once again no errors.

Here's an interesting variation:

var scrollbar:VerticalScrollbar = new VerticalScrollbar(stage, thumb, track, null, null, up, down);

That one caused the content not to scroll in the window, but the thumb could be moved manually or with the up and down buttons. Truly, if someone knows how to use this class, and sends it the proper arguments, they should be able to get out of it whatever behavior they want!

Our class is fully functional at this point, and nearly finished! On the next page, we'll make it dispatch events, add a few more little enhancements, and test it out.