Saturday, September 29, 2012

Creating Advanced Alert Window class: Part 27

In this tutorial we will polish our tab navigation functionality, fix some bugs, add a default button, and add a feature that auto-sizes the window (in height) depending on the size of the text provided, if provided window height is 0.

First we'll fix some tab navigation code problems.

Go to AdvAlertManager and find the buttonHandler function inside alert(), which is executed when a button is pressed.

Here, add a line that sets focusedButton back to -1. Also, in the line that sets topWindow value, we're currently referring to a "w" property, when it should be "window":

function buttonHandler(evt:MouseEvent):void {
if (closeHandler != null) closeHandler.call(evt.currentTarget, evt.currentTarget.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 go to kDown() function and find the second if...statement. Here, also add the focusedButton = -1 line and fix the topWindow line.

Moreover, change the parameters that are passed to closeHandler function when its called - send topWindow.buttons[focusedButton] and topWindow.buttons[focusedButton].txt.

Also, in the beginning of the if... statement, add another conditional that checks if focusedButton is -1. If so, set it to 0.

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;
if (closeHandler != null) closeHandler.call(topWindow.buttons[focusedButton], topWindow.buttons[focusedButton].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;
}
}

Full kDown() function:

private function kDown(evt:KeyboardEvent):void {
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;
if (closeHandler != null) closeHandler.call(topWindow.buttons[focusedButton], topWindow.buttons[focusedButton].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 add the ability to apply 0 value as window height, and autosize the window if 0 is specified. We will also have 0 as the default value. We'll need to do a few changes to achieve this.

First of all, go to alert() function and set default value of the height parameter to 0:

public function alert(text:String, title:String = "", buttons:Array = null, closeHandler:Function = null, width:int = 300, height:int = 0, position:Point = null):void {

Then find the line that declares AdvAlertWindow object, pass 2 more parameters to the constructor - pWidth and pHeight.

var w:AdvAlertWindow = new AdvAlertWindow(text, title, width, height, position, skin, buttons, bSkin, pWidth, pHeight);

Also, when checking of buttons array is null, instead of setting it to an empty array, set it to an array of 1 default button "OK":

if (buttons == null) buttons = [new AdvAlertButton("OK")];

Full AdvAlertManager code so far:

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;
/**
 * 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;

/**
 * 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.
 * @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.
 */

public function AdvAlertManager(defaultWindowContainer:DisplayObjectContainer, stage:Stage, windowSkin:AdvAlertSkin = null, buttonSkin:AdvAlertButtonSkin = null, disableTab:Boolean = true) 
{
trace(": AdvAlertManager instance created.");
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 = [];
if (tabDisable) {
stg.addEventListener(KeyboardEvent.KEY_DOWN, kDown);
}
}

private function kDown(evt:KeyboardEvent):void {
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;
if (closeHandler != null) closeHandler.call(topWindow.buttons[focusedButton], topWindow.buttons[focusedButton].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;
}

/**
 * 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. 300 by default.
 * @paramheight (Optional) Height of the alert window. If 0, window will be auto sized.
 * @paramposition (Optional) Coordinates of top-left corner of the alert window. If not specified - the window is centered.
 */

public function alert(text:String, title:String = "", buttons:Array = null, closeHandler:Function = null, width:int = 300, height:int = 0, position:Point = null):void {
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);
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 {
if (closeHandler != null) closeHandler.call(evt.currentTarget, evt.currentTarget.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 go to AdvAlertWindow class.

Here, declare 2 new variables - parw and parh (to store the width and height of the parent):

private var parw:int;
private var parh:int;

Receive the 2 new parameters in the constructor:

public function AdvAlertWindow(pt:String, ptt:String, pw:int, ph:int, ppos:Point, sk:AdvAlertSkin, bt:Array, defaultButtonSkin:AdvAlertButtonSkin, parentw:int, parenth:int) 

Apply the values in constructor too:

parw = parentw;
parh = parenth;

In the end of the constructor, check if ph (the height of the window) equals 0. If so, call a function autoSize():

if (ph == 0) autoSize();

Full constructor:

public function AdvAlertWindow(pt:String, ptt:String, pw:int, ph:int, ppos:Point, sk:AdvAlertSkin, bt:Array, defaultButtonSkin:AdvAlertButtonSkin, parentw:int, parenth:int) 
{
t = pt;
tt = ptt;
w = pw;
h = ph;
pos = ppos;
buttons = bt;
parw = parentw;
parh = parenth;

setSkin(sk);

textField = new TextField();
addChild(textField);

titleField = new TextField();
addChild(titleField);
for (var i:int = 0; i < buttons.length; i++) {
if (buttons[i].currentSkin == null) {
buttons[i].setSkin(defaultButtonSkin);
buttons[i].updateDraw();
}
addChild(buttons[i]);
}

updateDraw();

if (ph == 0) autoSize();
}

Create the autoSize() function, calculate the new height and y position of the window, then call updateDraw(). Use the textField's textHeight property in the calculations to find out how big the text is.

private function autoSize():void {
var textHeight:int = textField.textHeight;
h = headerHeight + headerPadding.top + headerPadding.bottom + buttonbarHeight + buttonbarPadding.top + buttonbarPadding.bottom + textPadding.top + textPadding.bottom + textHeight;
pos.y = parh / 2 - h / 2;
updateDraw();
}

Full AdvAlertWindow.as code:

package com.kircode.AdvAlert 
{
import flash.display.MovieClip;
import flash.events.Event;
import flash.geom.Point;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.display.GradientType;

/**
 * Advanced Alert window object.
 * @author Kirill Poletaev
 */
public class AdvAlertWindow extends MovieClip
{
private var t:String;
private var tt:String;
private var w:int;
private var h:int;
private var pos:Point;
private var parw:int;
private var parh:int;
public var buttons:Array;

private var textField:TextField;
private var titleField:TextField;

private var textPadding:TextPadding;
private var headerPadding:TextPadding;
private var titlePadding:TextPadding;
private var headerHeight:Number;
private var buttonbarPadding:TextPadding;
private var buttonbarHeight:Number;
private var titleFormat:TextFormat;
private var textFormat:TextFormat;
private var selectable:Boolean;
private var headerRect:*;
private var bgRect:*;
private var headerStroke:*;
private var bgStroke:*;
private var skinFilters:Array;
private var buttonInterval:Number;
private var defaultButtonSkin:AdvAlertButtonSkin;
private var buttonbarAlign:String;

public function AdvAlertWindow(pt:String, ptt:String, pw:int, ph:int, ppos:Point, sk:AdvAlertSkin, bt:Array, defaultButtonSkin:AdvAlertButtonSkin, parentw:int, parenth:int) 
{
t = pt;
tt = ptt;
w = pw;
h = ph;
pos = ppos;
buttons = bt;
parw = parentw;
parh = parenth;

setSkin(sk);

textField = new TextField();
addChild(textField);

titleField = new TextField();
addChild(titleField);
for (var i:int = 0; i < buttons.length; i++) {
if (buttons[i].currentSkin == null) {
buttons[i].setSkin(defaultButtonSkin);
buttons[i].updateDraw();
}
addChild(buttons[i]);
}

updateDraw();

if (ph == 0) autoSize();
}

public function setSkin(skin:AdvAlertSkin):void {
textPadding = skin.textPadding;
headerPadding = skin.headerPadding;
titlePadding = skin.titlePadding;
headerHeight = skin.headerHeight;
titleFormat = skin.titleFormat;
textFormat = skin.textFormat;
selectable = skin.selectable;
bgRect = skin.bgRect;
headerRect = skin.headerRect;
bgStroke = skin.bgStroke;
headerStroke = skin.headerStroke;
skinFilters = skin.filters;
buttonbarHeight = skin.buttonbarHeight;
buttonbarPadding = skin.buttonbarPadding;
buttonInterval = skin.buttonInterval;
buttonbarAlign = skin.buttonbarAlign;
}

private function autoSize():void {
var textHeight:int = textField.textHeight;
h = headerHeight + headerPadding.top + headerPadding.bottom + buttonbarHeight + buttonbarPadding.top + buttonbarPadding.bottom + textPadding.top + textPadding.bottom + textHeight;
pos.y = parh / 2 - h / 2;
updateDraw();
}

public function updateDraw():void {
this.graphics.clear();
// filters
this.filters = skinFilters;

// bg stroke
if (bgStroke is SolidColorStroke) {
this.graphics.lineStyle(bgStroke._lineThickness, bgStroke._lineColor, bgStroke._lineAlpha);
}
if (bgStroke is GradientColorStroke) {
this.graphics.lineStyle(bgStroke._lineThickness);
this.graphics.lineGradientStyle(bgStroke._gradientType, bgStroke._colors, bgStroke._alphas, bgStroke._ratios, bgStroke._matrix, bgStroke._spreadMethod, bgStroke._interpolationMethod, bgStroke._focalPointRatio);
}
if (bgStroke is BitmapStroke) {
this.graphics.lineStyle(bgStroke._lineThickness);
this.graphics.lineBitmapStyle(bgStroke._bitmap, bgStroke._matrix, bgStroke._repeat, bgStroke._smooth);
}

// bg fill
if (bgRect is SolidColorRect) this.graphics.beginFill(bgRect._backgroundColor, bgRect._alpha);
if (bgRect is GradientColorRect) this.graphics.beginGradientFill(bgRect._gradientType, bgRect._colors, bgRect._alphas, bgRect._ratios, bgRect._matrix, bgRect._spreadMethod, bgRect._interpolationMethod, bgRect._focalPointRatio);
if (bgRect is BitmapRect) this.graphics.beginBitmapFill(bgRect._bitmap, bgRect._matrix, bgRect._repeat, bgRect._smooth);

this.graphics.drawRoundRectComplex(pos.x, pos.y, w, h,
bgRect._radius[0], bgRect._radius[1], bgRect._radius[2], bgRect._radius[3]);
this.graphics.endFill();

// header stroke
if (headerStroke is SolidColorStroke) {
this.graphics.lineStyle(headerStroke._lineThickness, headerStroke._lineColor, headerStroke._lineAlpha);
}
if (headerStroke is GradientColorStroke) {
this.graphics.lineStyle(headerStroke._lineThickness);
this.graphics.lineGradientStyle(headerStroke._gradientType, headerStroke._colors, headerStroke._alphas, headerStroke._ratios, headerStroke._matrix, headerStroke._spreadMethod, headerStroke._interpolationMethod, headerStroke._focalPointRatio);
}
if (headerStroke is BitmapStroke) {
this.graphics.lineStyle(headerStroke._lineThickness);
this.graphics.lineBitmapStyle(headerStroke._bitmap, headerStroke._matrix, headerStroke._repeat, headerStroke._smooth);
}

// header fill
if (headerRect is SolidColorRect) this.graphics.beginFill(headerRect._backgroundColor, headerRect._alpha);
if (headerRect is GradientColorRect) this.graphics.beginGradientFill(headerRect._gradientType, headerRect._colors, headerRect._alphas, headerRect._ratios, headerRect._matrix, headerRect._spreadMethod, headerRect._interpolationMethod, headerRect._focalPointRatio);
if (headerRect is BitmapRect) this.graphics.beginBitmapFill(headerRect._bitmap, headerRect._matrix, headerRect._repeat, headerRect._smooth);

this.graphics.drawRoundRectComplex(pos.x + headerPadding.left, pos.y + headerPadding.top, w - (headerPadding.left + headerPadding.right), headerHeight,
headerRect._radius[0], headerRect._radius[1], headerRect._radius[2], headerRect._radius[3]);
this.graphics.endFill();

// title
titleField.width = w - (headerPadding.left + headerPadding.right);
titleField.text = tt;
titleField.height = headerHeight;
titleField.x = pos.x + headerPadding.left + titlePadding.left;
titleField.y = pos.y + headerPadding.top + titlePadding.top;

// text
textField.width = w - (textPadding.right + textPadding.left);
textField.height = h - (textPadding.top + textPadding.bottom + headerPadding.top + headerPadding.bottom + headerHeight + buttonbarHeight + buttonbarPadding.top + buttonbarPadding.bottom);
textField.text = t;
textField.x = pos.x + textPadding.right;
textField.y = pos.y + textPadding.top + headerHeight + headerPadding.bottom + headerPadding.top;

// formats
textField.setTextFormat(textFormat);
titleField.setTextFormat(titleFormat);
textField.selectable = selectable;
titleField.selectable = selectable;
textField.multiline = true;
textField.wordWrap = true;

// buttons
var xCoord:Number;
var i:int

// right align
if(buttonbarAlign=="right"){
if (buttons.length > 0) xCoord = pos.x + w - buttons[buttons.length-1].w - buttonbarPadding.right;
for (i = buttons.length-1; i >= 0; i--) {
if (i != buttons.length-1) xCoord -= buttons[i].w + buttonInterval;
buttons[i].x = xCoord;
buttons[i].y = pos.y + h - buttons[i].h - buttonbarPadding.bottom;
}
}

// left align
if(buttonbarAlign=="left"){
if (buttons.length > 0) xCoord = pos.x + buttonbarPadding.left;
for (i = 0; i < buttons.length; i++) {
if (i > 0) xCoord += buttons[i].w + buttonInterval;
buttons[i].x = xCoord;
buttons[i].y = pos.y + h - buttons[i].h - buttonbarPadding.bottom;
}
}

// center align
if(buttonbarAlign=="center"){
if (buttons.length > 0) {
var buttonbarWidth:Number = 0;
for (i = 0; i < buttons.length; i++) {
buttonbarWidth += buttons[i].w + buttonInterval;
}
buttonbarWidth -= buttonInterval;
xCoord = pos.x + (w/2) - buttonbarWidth/2;
}
for (i = 0; i < buttons.length; i++) {
if (i > 0) xCoord += buttons[i].w + buttonInterval;
buttons[i].x = xCoord;
buttons[i].y = pos.y + h - buttons[i].h - buttonbarPadding.bottom;
}
}
}


}

}

Now you can go to main.as and see the code in action by creating alert windows of different text size, with unspecified height and buttons:

AlertManager = new AdvAlertManager(this, stage);
AlertManager.alert("This is an alert window with the default skin.", "Example alert!");
AlertManager.alert("This is an alert window with the default skin. More text here! More text here! More text here! More text here! More text here!More text here! More text here!", "Example alert 2!");

You can see that they are both navigateable with tab/enter, both are automatically sized to fit the text, both have default OK button.

That's all for now, thanks for reading!

No comments:

Post a Comment