Wednesday, October 10, 2012

Creating Advanced Alert Window class: Part 38

Today we will fix an issue related to alert button navigation.

Right now the tab navigation in alert windows works well, and so does mouse navigation, however, only if there is no close handler set. If there is a close function set by the developer, when the user clicks a button the program throws an error.

The error lies in the closeWindow() function of AdvAlertManager class - the reference to the currently selected button is null, because the focusedButton value doesn't get updated, and the reference as it is now relies on that value to refer to the correct button.

Let's rewrite the closeWindow() function, so that we can simply pass a ready currentButton reference to the function and use that in the first line of the function:

private function closeWindow(currentIndex:int, closeHandler:Function, currentButton:AdvAlertButton):void {
if (closeHandler != null && currentButton != null) closeHandler.call(currentButton, currentButton.txt);
windows[currentIndex].window.parent.removeChild(windows[currentIndex].window);
windows[currentIndex].blur.parent.removeChild(windows[currentIndex].blur);
windows.splice(currentIndex, 1);
focusedButton = -1;
if (windows.length > 0) topWindow = windows[windows.length - 1].window;
if (windows.length == 0) topWindow = null;
if (tabDisable && windows.length==0) {
defaultContainer.tabChildren = true;
defaultContainer.tabEnabled = true;
}
}

Now we need to go to all the lines that call closeWindow() and update them by providing another parameter.

In closeLast() function, we can simply pass null as the value - since no button is really clicked.

public function closeLast():void {
var currentIndex:int = windows.length - 1;
var closeHandler:Function = windows[currentIndex].onclose;
closeWindow(currentIndex, closeHandler, null);
}

Go to kDown() function, here we pass topWindow.buttons[focusedButton] value as the parameter:

private function kDown(evt:KeyboardEvent):void {
if(tabDisable){
if (evt.keyCode == 9 && topWindow!=null && topWindow.buttons.length>0) {
focusedButton++;
if (focusedButton >= topWindow.buttons.length) {
focusedButton = 0;
}
for (var i:int = 0; i < topWindow.buttons.length; i++) {
topWindow.buttons[i].over = false;
topWindow.buttons[i].updateDraw();
}
topWindow.buttons[focusedButton].over = true;
topWindow.buttons[focusedButton].updateDraw();
}
if (evt.keyCode == 13 && topWindow != null && topWindow.buttons.length > 0) {
if (focusedButton == -1) {
focusedButton = 0;
}
var currentIndex:int = windows.length - 1;
var closeHandler:Function = windows[currentIndex].onclose;
closeWindow(currentIndex, closeHandler, topWindow.buttons[focusedButton]);
}
}
}

In alert() function, find the buttonHandler() function which calls closeWindow(), and add the third parameter as shown below:

function buttonHandler(evt:MouseEvent):void {
closeWindow(currentIndex, closeHandler, evt.currentTarget as AdvAlertButton);
}

As you can see, I simply supplied the click target to the function.

The navigation should now work properly.

Full AdvAlertManager class:

package com.kircode.AdvAlert 
{
import flash.display.DisplayObjectContainer;
import flash.display.MovieClip;
import flash.display.Stage;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.media.Sound;
/**
 * Advanced Alert window manager.
 * @author Kirill Poletaev
 */
public class AdvAlertManager 
{
private var windows:Array;
private var defaultContainer:DisplayObjectContainer;
private var pWidth:int;
private var pHeight:int;
private var skin:AdvAlertSkin;
private var bSkin:AdvAlertButtonSkin;
private var tabDisable:Boolean;
private var topWindow:AdvAlertWindow;
private var focusedButton:int;
private var stg:Stage;
private var openingSound:Sound;
private var defWidth:int;
private var defHeight:int;
private var defMinHeight:int;
private var defMaxHeight:int;
private var defMaxWidth:int;

/**
 * AdvAlertManager constructor.
 * @paramdefaultWindowContainer Parent container of alert windows.
 * @paramstage Stage reference.
 * @paramwindowSkin Default skin for alert windows.
 * @parambuttonSkin Default skin for buttons in this window.
 * @paramopenSound Default window open sound.
 * @paramdisableTab Disable tab focusing on the parent container when an alert window is visible. This also enables tab navigation for the alert window's buttons.
 * @paramdefaultWidth Set default width for all window alerts. 300 by default.
 * @paramdefaultHeight Set default height for all window alerts. 0 by default. If 0, the window is auto-sized based on the text length.
 * @paramdefaultMinHeight Set default minimal height for all window alerts. 0 by default.
 * @paramdefaultMaxHeight Set default maximal height for all window alerts. 0 by default. If 0, automatically set to the height of the stage.
 * @paramdefaultMaxWidth Set default maximal width for all window alerts. 0 by default. If 0, automatically set to the width of the stage.
 */

public function AdvAlertManager(defaultWindowContainer:DisplayObjectContainer, stage:Stage, windowSkin:AdvAlertSkin = null, buttonSkin:AdvAlertButtonSkin = null, openSound:Sound = null, disableTab:Boolean = true, defaultWidth:int = 300, defaultHeight:int = 0, defaultMinHeight:int = 0, defaultMaxHeight:int = 0, defaultMaxWidth:int = 0) 
{
defWidth = defaultWidth;
defHeight = defaultHeight;
defMinHeight = defaultMinHeight;
defMaxHeight = defaultMaxHeight;
defMaxWidth = defaultMaxWidth;
if (defMaxHeight == 0) defMaxHeight = stage.stageHeight;
if (defMaxWidth == 0) defMaxWidth = stage.stageWidth;
openingSound = openSound;
skin = windowSkin;
bSkin = buttonSkin;
tabDisable = disableTab;
if (skin == null) skin = new AdvAlertSkin();
if (bSkin == null) bSkin = new AdvAlertButtonSkin();
defaultContainer = defaultWindowContainer;
pWidth = stage.stageWidth;
pHeight = stage.stageHeight;
stg = stage;
windows = [];
stg.addEventListener(KeyboardEvent.KEY_DOWN, kDown);
}

private function kDown(evt:KeyboardEvent):void {
if(tabDisable){
if (evt.keyCode == 9 && topWindow!=null && topWindow.buttons.length>0) {
focusedButton++;
if (focusedButton >= topWindow.buttons.length) {
focusedButton = 0;
}
for (var i:int = 0; i < topWindow.buttons.length; i++) {
topWindow.buttons[i].over = false;
topWindow.buttons[i].updateDraw();
}
topWindow.buttons[focusedButton].over = true;
topWindow.buttons[focusedButton].updateDraw();
}
if (evt.keyCode == 13 && topWindow != null && topWindow.buttons.length > 0) {
if (focusedButton == -1) {
focusedButton = 0;
}
var currentIndex:int = windows.length - 1;
var closeHandler:Function = windows[currentIndex].onclose;
closeWindow(currentIndex, closeHandler, topWindow.buttons[focusedButton]);
}
}
}

/**
 * Close the top alert window.
 */

public function closeLast():void {
var currentIndex:int = windows.length - 1;
var closeHandler:Function = windows[currentIndex].onclose;
closeWindow(currentIndex, closeHandler, null);
}

private function closeWindow(currentIndex:int, closeHandler:Function, currentButton:AdvAlertButton):void {
if (closeHandler != null && currentButton != null) closeHandler.call(currentButton, currentButton.txt);
windows[currentIndex].window.parent.removeChild(windows[currentIndex].window);
windows[currentIndex].blur.parent.removeChild(windows[currentIndex].blur);
windows.splice(currentIndex, 1);
focusedButton = -1;
if (windows.length > 0) topWindow = windows[windows.length - 1].window;
if (windows.length == 0) topWindow = null;
if (tabDisable && windows.length==0) {
defaultContainer.tabChildren = true;
defaultContainer.tabEnabled = true;
}
}

/**
 * Set skin of all future created alert windows.
 * @paramwindowSkin Reference to an AdvAlertSkin object.
 */

public function setSkin(windowSkin:AdvAlertSkin):void {
skin = windowSkin;
}

/**
 * Set skin of all buttons of future created alert windows.
 * @parambuttonSkin Reference to an AdvAlertButtonSkin object.
 */

public function setButtonSkin(buttonSkin:AdvAlertButtonSkin):void {
bSkin = buttonSkin;
}

/**
 * Set new options for the alert manager object - has the same parameters as the constructor.
 * @paramdefaultWindowContainer Parent container of alert windows.
 * @paramstage Stage reference.
 * @paramwindowSkin Default skin for alert windows.
 * @parambuttonSkin Default skin for buttons in this window.
 * @paramopenSound Default window open sound.
 * @paramdisableTab Disable tab focusing on the parent container when an alert window is visible. This also enables tab navigation for the alert window's buttons.
 * @paramdefaultWidth Set default width for all window alerts. 300 by default.
 * @paramdefaultHeight Set default height for all window alerts. 0 by default. If 0, the window is auto-sized based on the text length.
 * @paramdefaultMinHeight Set default minimal height for all window alerts. 0 by default.
 * @paramdefaultMaxHeight Set default maximal height for all window alerts. 0 by default. If 0, automatically set to the height of the parent.
 * @paramdefaultMaxWidth Set default maximal width for all window alerts. 0 by default. If 0, automatically set to the width of the stage.
 */

public function reset(defaultWindowContainer:DisplayObjectContainer, stage:Stage, windowSkin:AdvAlertSkin = null, buttonSkin:AdvAlertButtonSkin = null, openSound:Sound = null, disableTab:Boolean = true, defaultWidth:int = 300, defaultHeight:int = 0, defaultMinHeight:int = 0, defaultMaxHeight:int = 0, defaultMaxWidth:int = 0) 
{
defWidth = defaultWidth;
defHeight = defaultHeight;
defMinHeight = defaultMinHeight;
defMaxHeight = defaultMaxHeight;
defMaxWidth = defaultMaxWidth;
if (defMaxHeight == 0) defMaxHeight = stage.stageHeight;
if (defMaxWidth == 0) defMaxWidth = stage.stageWidth;
openingSound = openSound;
skin = windowSkin;
bSkin = buttonSkin;
tabDisable = disableTab;
if (skin == null) skin = new AdvAlertSkin();
if (bSkin == null) bSkin = new AdvAlertButtonSkin();
defaultContainer = defaultWindowContainer;
pWidth = stage.stageWidth;
pHeight = stage.stageHeight;
stg = stage;
}

/**
 * Create an alert window.
 * @paramtext Text value of the alert window.
 * @paramtitle Title value of the alert window.
 * @parambuttons (Optional) Array of AdvAlertButton objects that represent buttons in the alert window.
 * @paramcloseHandler (Optional) Close handling function. Must receive a string value as parameter (which will hold the label of the button that was clicked).
 * @paramwidth (Optional) Width of the alert window. If 0, set to default.
 * @paramheight (Optional) Height of the alert window. If 0, set to default. If default is 0, window will be auto sized.
 * @paramposition (Optional) Coordinates of top-left corner of the alert window. If not specified - the window is centered.
 * @paramminHeight (Optional) The minimum height value of the alert window. If 0, set to default.
 * @parammaxHeight (Optional) The maximum height value of the alert window. If 0, set to default.
 * @parammaxWidth (Optional) The maximum width value of the alert window. If 0, set to default.
 * @paramhorizontalStretch (Optional) If set to true, will stretch the window horizontally if the text doesn't fit and the height has already reached maxHeight.
 * @paramopenSound (Optional) Sound to play when the window opens. Specified default sound plays by default.
 */

public function alert(text:String, title:String = "", buttons:Array = null, closeHandler:Function = null, width:int = 0, height:int = 0, position:Point = null, minHeight:int = 0, maxHeight:int = 0, maxWidth:int = 0, horizontalStretch:Boolean = true, openSound:Sound = null):AdvAlertWindow {
if (width == 0) width = defWidth;
if (height == 0) height = defHeight;
if (minHeight == 0) minHeight = defMinHeight;
if (maxHeight == 0) maxHeight = defMaxHeight;
if (maxWidth == 0) maxWidth = defMaxWidth;
if (openSound != null) {
openSound.play();
}else
if (openingSound != null) {
openingSound.play();
}
if (position == null) position = new Point((pWidth / 2) - (width / 2), (pHeight / 2) - (height / 2));
if (buttons == null) buttons = [new AdvAlertButton("OK")];
for (var i:int = buttons.length - 1; i >= 0; i--) {
if (!buttons[i] is AdvAlertButton) {
throw new Error("An item in 'buttons' array is not an AdvAlertButton instance. Ignoring...");
buttons.splice(i, 1);
}else {
buttons[i].addEventListener(MouseEvent.CLICK, buttonHandler);
}
}
var w:AdvAlertWindow = new AdvAlertWindow(text, title, width, height, position, skin, buttons, bSkin, pWidth, pHeight, minHeight, maxHeight, maxWidth, horizontalStretch);
var b:AdvAlertBlur = new AdvAlertBlur(pWidth, pHeight, skin);
var currentIndex:int = windows.length;
windows.push( {window:w, blur:b, onclose:closeHandler} );
defaultContainer.addChild(b);
defaultContainer.addChild(w);

topWindow = w;
focusedButton = -1;
if (tabDisable) {
defaultContainer.tabChildren = false;
defaultContainer.tabEnabled = false;
}

function buttonHandler(evt:MouseEvent):void {
closeWindow(currentIndex, closeHandler, evt.currentTarget as AdvAlertButton);
}
return w;
}

}

}

Let's go to main.as and test it out. Here I create a customized alert window with 3 buttons and a close event handler:

private function init(evt:Event):void {
var mySkin:AdvAlertSkin = new AdvAlertSkin();
mySkin.addContent(warning_icon, new Point(5, 40));
mySkin.textPadding.left = 140;
mySkin.titleFilters = [new DropShadowFilter(0, 0, 0, 1, 3, 3, 2, 3)];
mySkin.textFilters = [new DropShadowFilter(1, 45, 0, 0.4, 0, 0, 5, 1)];

var myButtonSkin:AdvAlertButtonSkin = new AdvAlertButtonSkin();
myButtonSkin.textFilters = [new DropShadowFilter(1, 45, 0, 0.4, 0, 0, 5, 1)];
myButtonSkin.hover_textFilters = [new DropShadowFilter(1, 45, 0, 0.4, 0, 0, 5, 1), new DropShadowFilter(0, 0, 0, 1, 3, 3, 1, 3)];

AlertManager = new AdvAlertManager(this, stage, mySkin, myButtonSkin);
AlertManager.alert("A window with 3 buttons.", "Example alert!", [new AdvAlertButton("One"), new AdvAlertButton("Two"), new AdvAlertButton("Three")], onClose, 300, 200);
}

private function onClose(str:String):void {
trace(str);
}

You can use either mouse or keyboard to navigate through the buttons, and everything will work as it is meant to.

Thanks for reading!

No comments:

Post a Comment