scaffold.libs_as.feathers.controls.Alert.as Maven / Gradle / Ivy
/*
Feathers
Copyright 2012-2015 Bowler Hat LLC. All Rights Reserved.
This program is free software. You can redistribute and/or modify it in
accordance with the terms of the accompanying license agreement.
*/
package feathers.controls
{
import feathers.core.FeathersControl;
import feathers.core.IFeathersControl;
import feathers.core.ITextRenderer;
import feathers.core.IValidating;
import feathers.core.PopUpManager;
import feathers.core.PropertyProxy;
import feathers.data.ListCollection;
import feathers.events.FeathersEventType;
import feathers.layout.HorizontalAlign;
import feathers.layout.VerticalLayout;
import feathers.skins.IStyleProvider;
import starling.display.DisplayObject;
import starling.events.Event;
[Exclude(name="layout",kind="property")]
[Exclude(name="footer",kind="property")]
[Exclude(name="footerFactory",kind="property")]
[Exclude(name="footerProperties",kind="property")]
[Exclude(name="customFooterName",kind="property")]
[Exclude(name="customFooterStyleName",kind="property")]
[Exclude(name="createFooter",kind="method")]
/**
* Dispatched when the alert is closed. The data
property of
* the event object will contain the item from the ButtonGroup
* data provider for the button that is triggered. If no button is
* triggered, then the data
property will be null
.
*
* The properties of the event object have the following values:
*
* Property Value
* bubbles
false
* currentTarget
The Object that defines the
* event listener that handles the event. For example, if you use
* myButton.addEventListener()
to register an event listener,
* myButton is the value of the currentTarget
.
* data
null
* target
The Object that dispatched the event;
* it is not always the Object listening for the event. Use the
* currentTarget
property to always access the Object
* listening for the event.
*
*
* @eventType starling.events.Event.CLOSE
*/
[Event(name="close",type="starling.events.Event")]
/**
* Displays a message in a modal pop-up with a title and a set of buttons.
*
* In general, an Alert
isn't instantiated directly.
* Instead, you will typically call the static function
* Alert.show()
. This is not required, but it result in less
* code and no need to manually manage calls to the PopUpManager
.
*
* In the following example, an alert is shown when a Button
* is triggered:
*
*
* button.addEventListener( Event.TRIGGERED, button_triggeredHandler );
*
* function button_triggeredHandler( event:Event ):void
* {
* var alert:Alert = Alert.show( "This is an alert!", "Hello World", new ListCollection(
* [
* { label: "OK" }
* ]));
* }
*
* @see ../../../help/alert.html How to use the Feathers Alert component
*/
public class Alert extends Panel
{
/**
* The default value added to the styleNameList
of the header.
*
* @see feathers.core.FeathersControl#styleNameList
*/
public static const DEFAULT_CHILD_STYLE_NAME_HEADER:String = "feathers-alert-header";
/**
* The default value added to the styleNameList
of the button group.
*
* @see feathers.core.FeathersControl#styleNameList
*/
public static const DEFAULT_CHILD_STYLE_NAME_BUTTON_GROUP:String = "feathers-alert-button-group";
/**
* The default value added to the styleNameList
of the message.
*
* @see feathers.core.FeathersControl#styleNameList
*/
public static const DEFAULT_CHILD_STYLE_NAME_MESSAGE:String = "feathers-alert-message";
/**
* Returns a new Alert
instance when Alert.show()
* is called. If one wishes to skin the alert manually, a custom factory
* may be provided.
*
* This function is expected to have the following signature:
*
* function():Alert
*
* The following example shows how to create a custom alert factory:
*
*
* Alert.alertFactory = function():Alert
* {
* var alert:Alert = new Alert();
* //set properties here!
* return alert;
* };
*
* @see #show()
*/
public static var alertFactory:Function = defaultAlertFactory;
/**
* Returns an overlay to display with a alert that is modal. Uses the
* standard overlayFactory
of the PopUpManager
* by default, but you can use this property to provide your own custom
* overlay, if you prefer.
*
* This function is expected to have the following signature:
* function():DisplayObject
*
* The following example uses a semi-transparent Quad
as
* a custom overlay:
*
*
* Alert.overlayFactory = function():Quad
* {
* var quad:Quad = new Quad(10, 10, 0x000000);
* quad.alpha = 0.75;
* return quad;
* };
*
* @see feathers.core.PopUpManager#overlayFactory
*
* @see #show()
*/
public static var overlayFactory:Function;
/**
* The default IStyleProvider
for all Alert
* components.
*
* @default null
* @see feathers.core.FeathersControl#styleProvider
*/
public static var globalStyleProvider:IStyleProvider;
/**
* The default factory that creates alerts when Alert.show()
* is called. To use a different factory, you need to set
* Alert.alertFactory
to a Function
* instance.
*
* @see #show()
* @see #alertFactory
*/
public static function defaultAlertFactory():Alert
{
return new Alert();
}
/**
* Creates an alert, sets common properties, and adds it to the
* PopUpManager
with the specified modal and centering
* options.
*
* In the following example, an alert is shown when a
* Button
is triggered:
*
*
* button.addEventListener( Event.TRIGGERED, button_triggeredHandler );
*
* function button_triggeredHandler( event:Event ):void
* {
* var alert:Alert = Alert.show( "This is an alert!", "Hello World", new ListCollection(
* [
* { label: "OK" }
* ]);
* }
*/
public static function show(message:String, title:String = null, buttons:ListCollection = null,
icon:DisplayObject = null, isModal:Boolean = true, isCentered:Boolean = true,
customAlertFactory:Function = null, customOverlayFactory:Function = null):Alert
{
var factory:Function = customAlertFactory;
if(factory == null)
{
factory = alertFactory != null ? alertFactory : defaultAlertFactory;
}
var alert:Alert = Alert(factory());
alert.title = title;
alert.message = message;
alert.buttonsDataProvider = buttons;
alert.icon = icon;
factory = customOverlayFactory;
if(factory == null)
{
factory = overlayFactory;
}
PopUpManager.addPopUp(alert, isModal, isCentered, factory);
return alert;
}
/**
* @private
*/
protected static function defaultButtonGroupFactory():ButtonGroup
{
return new ButtonGroup();
}
/**
* Constructor.
*/
public function Alert()
{
super();
this.headerStyleName = DEFAULT_CHILD_STYLE_NAME_HEADER;
this.footerStyleName = DEFAULT_CHILD_STYLE_NAME_BUTTON_GROUP;
this.buttonGroupFactory = defaultButtonGroupFactory;
}
/**
* The value added to the styleNameList
of the alert's
* message text renderer. This variable is protected
so
* that sub-classes can customize the message style name in their
* constructors instead of using the default style name defined by
* DEFAULT_CHILD_STYLE_NAME_MESSAGE
.
*
* @see feathers.core.FeathersControl#styleNameList
*/
protected var messageStyleName:String = DEFAULT_CHILD_STYLE_NAME_MESSAGE;
/**
* The header sub-component.
*
* For internal use in subclasses.
*/
protected var headerHeader:Header;
/**
* The button group sub-component.
*
* For internal use in subclasses.
*/
protected var buttonGroupFooter:ButtonGroup;
/**
* The message text renderer sub-component.
*
* For internal use in subclasses.
*/
protected var messageTextRenderer:ITextRenderer;
/**
* @private
*/
override protected function get defaultStyleProvider():IStyleProvider
{
return Alert.globalStyleProvider;
}
/**
* @private
*/
protected var _message:String = null;
/**
* The alert's main text content.
*/
public function get message():String
{
return this._message;
}
/**
* @private
*/
public function set message(value:String):void
{
if(this._message == value)
{
return;
}
this._message = value;
this.invalidate(INVALIDATION_FLAG_DATA);
}
/**
* @private
*/
protected var _icon:DisplayObject;
/**
* The alert's optional icon content to display next to the text.
*/
public function get icon():DisplayObject
{
return this._icon;
}
/**
* @private
*/
public function set icon(value:DisplayObject):void
{
if(this._icon == value)
{
return;
}
var oldDisplayListBypassEnabled:Boolean = this.displayListBypassEnabled;
this.displayListBypassEnabled = false;
if(this._icon)
{
this._icon.removeEventListener(FeathersEventType.RESIZE, icon_resizeHandler);
this.removeChild(this._icon);
}
this._icon = value;
if(this._icon)
{
this._icon.addEventListener(FeathersEventType.RESIZE, icon_resizeHandler);
this.addChild(this._icon);
}
this.displayListBypassEnabled = oldDisplayListBypassEnabled;
this.invalidate(INVALIDATION_FLAG_DATA);
}
/**
* @private
*/
protected var _gap:Number = 0;
/**
* The space, in pixels, between the alert's icon and its message text
* renderer.
*
* In the following example, the gap is set to 20 pixels:
*
*
* alert.gap = 20;
*
* @default 0
*/
public function get gap():Number
{
return this._gap;
}
/**
* @private
*/
public function set gap(value:Number):void
{
if(this._gap == value)
{
return;
}
this._gap = value;
this.invalidate(INVALIDATION_FLAG_LAYOUT);
}
/**
* @private
*/
protected var _buttonsDataProvider:ListCollection;
/**
* The data provider of the alert's ButtonGroup
.
*/
public function get buttonsDataProvider():ListCollection
{
return this._buttonsDataProvider;
}
/**
* @private
*/
public function set buttonsDataProvider(value:ListCollection):void
{
if(this._buttonsDataProvider == value)
{
return;
}
this._buttonsDataProvider = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _messageFactory:Function;
/**
* A function used to instantiate the alert's message text renderer
* sub-component. By default, the alert will use the global text
* renderer factory, FeathersControl.defaultTextRendererFactory()
,
* to create the message text renderer. The message text renderer must
* be an instance of ITextRenderer
. This factory can be
* used to change properties on the message text renderer when it is
* first created. For instance, if you are skinning Feathers components
* without a theme, you might use this factory to style the message text
* renderer.
*
* If you are not using a theme, the message factory can be used to
* provide skin the message text renderer with appropriate text styles.
*
* The factory should have the following function signature:
* function():ITextRenderer
*
* In the following example, a custom message factory is passed to
* the alert:
*
*
* alert.messageFactory = function():ITextRenderer
* {
* var messageRenderer:TextFieldTextRenderer = new TextFieldTextRenderer();
* messageRenderer.textFormat = new TextFormat( "_sans", 12, 0xff0000 );
* return messageRenderer;
* }
*
* @default null
*
* @see #message
* @see feathers.core.ITextRenderer
* @see feathers.core.FeathersControl#defaultTextRendererFactory
*/
public function get messageFactory():Function
{
return this._messageFactory;
}
/**
* @private
*/
public function set messageFactory(value:Function):void
{
if(this._messageFactory == value)
{
return;
}
this._messageFactory = value;
this.invalidate(INVALIDATION_FLAG_TEXT_RENDERER);
}
/**
* @private
*/
protected var _messageProperties:PropertyProxy;
/**
* An object that stores properties for the alert's message text
* renderer sub-component, and the properties will be passed down to the
* text renderer when the alert validates. The available properties
* depend on which ITextRenderer
implementation is returned
* by messageFactory
. Refer to
* feathers.core.ITextRenderer
* for a list of available text renderer implementations.
*
* In the following example, some properties are set for the alert's
* message text renderer (this example assumes that the message text
* renderer is a BitmapFontTextRenderer
):
*
*
* alert.messageProperties.textFormat = new BitmapFontTextFormat( bitmapFont );
* alert.messageProperties.wordWrap = true;
*
* If the subcomponent has its own subcomponents, their properties
* can be set too, using attribute @
notation. For example,
* to set the skin on the thumb which is in a SimpleScrollBar
,
* which is in a List
, you can use the following syntax:
* list.verticalScrollBarProperties.@thumbProperties.defaultSkin = new Image(texture);
*
* Setting properties in a messageFactory
function instead
* of using messageProperties
will result in better
* performance.
*
* @default null
*
* @see #messageFactory
* @see feathers.core.ITextRenderer
*/
public function get messageProperties():Object
{
if(!this._messageProperties)
{
this._messageProperties = new PropertyProxy(childProperties_onChange);
}
return this._messageProperties;
}
/**
* @private
*/
public function set messageProperties(value:Object):void
{
if(this._messageProperties == value)
{
return;
}
if(value && !(value is PropertyProxy))
{
value = PropertyProxy.fromObject(value);
}
if(this._messageProperties)
{
this._messageProperties.removeOnChangeCallback(childProperties_onChange);
}
this._messageProperties = PropertyProxy(value);
if(this._messageProperties)
{
this._messageProperties.addOnChangeCallback(childProperties_onChange);
}
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _customMessageStyleName:String;
/**
* A style name to add to the alert's message text renderer
* sub-component. Typically used by a theme to provide different styles
* to different alerts.
*
* In the following example, a custom message style name is passed
* to the alert:
*
*
* alert.customMessageStyleName = "my-custom-button-group";
*
* In your theme, you can target this sub-component style name to
* provide different styles than the default:
*
*
* getStyleProviderForClass( BitmapFontTextRenderer ).setFunctionForStyleName( "my-custom-message", setCustomMessageStyles );
*
* @default null
*
* @see #DEFAULT_CHILD_STYLE_NAME_MESSAGE
* @see feathers.core.FeathersControl#styleNameList
* @see #messageFactory
* @see #messageProperties
*/
public function get customMessageStyleName():String
{
return this._customMessageStyleName;
}
/**
* @private
*/
public function set customMessageStyleName(value:String):void
{
if(this._customMessageStyleName == value)
{
return;
}
this._customMessageStyleName = value;
this.invalidate(INVALIDATION_FLAG_TEXT_RENDERER);
}
/**
* A function used to generate the alerts's button group sub-component.
* The button group must be an instance of ButtonGroup
.
* This factory can be used to change properties on the button group
* when it is first created. For instance, if you are skinning Feathers
* components without a theme, you might use this factory to set skins
* and other styles on the button group.
*
* The function should have the following signature:
* function():ButtonGroup
*
* In the following example, a custom button group factory is
* provided to the alert:
*
*
* alert.buttonGroupFactory = function():ButtonGroup
* {
* return new ButtonGroup();
* };
*
* @default null
*
* @see feathers.controls.ButtonGroup
* @see #buttonGroupProperties
*/
public function get buttonGroupFactory():Function
{
return super.footerFactory;
}
/**
* @private
*/
public function set buttonGroupFactory(value:Function):void
{
super.footerFactory = value;
}
/**
* A style name to add to the alert's button group sub-component.
* Typically used by a theme to provide different styles to different alerts.
*
* In the following example, a custom button group style name is
* passed to the alert:
*
*
* alert.customButtonGroupStyleName = "my-custom-button-group";
*
* In your theme, you can target this sub-component style name to
* provide different styles than the default:
*
*
* getStyleProviderForClass( ButtonGroup ).setFunctionForStyleName( "my-custom-button-group", setCustomButtonGroupStyles );
*
* @default null
*
* @see #DEFAULT_CHILD_STYLE_NAME_BUTTON_GROUP
* @see feathers.core.FeathersControl#styleNameList
* @see #buttonGroupFactory
* @see #buttonGroupProperties
*/
public function get customButtonGroupStyleName():String
{
return super.customFooterStyleName;
}
/**
* @private
*/
public function set customButtonGroupStyleName(value:String):void
{
super.customFooterStyleName = value;
}
/**
* An object that stores properties for the alert's button group
* sub-component, and the properties will be passed down to the button
* group when the alert validates. For a list of available properties,
* refer to feathers.controls.ButtonGroup
.
*
* If the subcomponent has its own subcomponents, their properties
* can be set too, using attribute @
notation. For example,
* to set the skin on the thumb which is in a SimpleScrollBar
,
* which is in a List
, you can use the following syntax:
* list.verticalScrollBarProperties.@thumbProperties.defaultSkin = new Image(texture);
*
* Setting properties in a buttonGroupFactory
function
* instead of using buttonGroupProperties
will result in better
* performance.
*
* In the following example, the button group properties are customized:
*
*
* alert.buttonGroupProperties.gap = 20;
*
* @default null
*
* @see #buttonGroupFactory
* @see feathers.controls.ButtonGroup
*/
public function get buttonGroupProperties():Object
{
return super.footerProperties;
}
/**
* @private
*/
public function set buttonGroupProperties(value:Object):void
{
super.footerProperties = value;
}
/**
* @private
*/
override protected function initialize():void
{
if(!this.layout)
{
var layout:VerticalLayout = new VerticalLayout();
layout.horizontalAlign = HorizontalAlign.JUSTIFY;
this.layout = layout;
}
super.initialize();
}
/**
* @private
*/
override protected function draw():void
{
var dataInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_DATA);
var stylesInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STYLES)
var textRendererInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_TEXT_RENDERER);
if(textRendererInvalid)
{
this.createMessage();
}
if(textRendererInvalid || dataInvalid)
{
this.messageTextRenderer.text = this._message;
}
if(textRendererInvalid || stylesInvalid)
{
this.refreshMessageStyles();
}
super.draw();
if(this._icon)
{
if(this._icon is IValidating)
{
IValidating(this._icon).validate();
}
this._icon.x = this._paddingLeft;
this._icon.y = this._topViewPortOffset + (this._viewPort.visibleHeight - this._icon.height) / 2;
}
}
/**
* @private
*/
override protected function autoSizeIfNeeded():Boolean
{
var needsWidth:Boolean = this._explicitWidth !== this._explicitWidth; //isNaN
var needsHeight:Boolean = this._explicitHeight !== this._explicitHeight; //isNaN
if(!needsWidth && !needsHeight)
{
return false;
}
if(this._icon is IValidating)
{
IValidating(this._icon).validate();
}
var oldIgnoreHeaderResizing:Boolean = this._ignoreHeaderResizing;
this._ignoreHeaderResizing = true;
var oldIgnoreFooterResizing:Boolean = this._ignoreFooterResizing;
this._ignoreFooterResizing = true;
var oldHeaderWidth:Number = this.header.width;
var oldHeaderHeight:Number = this.header.height;
this.header.width = this._explicitWidth;
this.header.maxWidth = this._maxWidth;
this.header.height = NaN;
this.header.validate();
if(this.footer)
{
var oldFooterWidth:Number = this.footer.width;
var oldFooterHeight:Number = this.footer.height;
this.footer.width = this._explicitWidth;
this.footer.maxWidth = this._maxWidth;
this.footer.height = NaN;
this.footer.validate();
}
var newWidth:Number = this._explicitWidth;
var newHeight:Number = this._explicitHeight;
if(needsWidth)
{
newWidth = this._viewPort.width + this._rightViewPortOffset + this._leftViewPortOffset;
if(this._icon)
{
var iconWidth:Number = this._icon.width;
if(iconWidth === iconWidth) //!isNaN
{
newWidth += this._icon.width + this._gap;
}
}
newWidth = Math.max(newWidth, this.header.width);
if(this.footer)
{
newWidth = Math.max(newWidth, this.footer.width);
}
if(this.originalBackgroundWidth === this.originalBackgroundWidth && //!isNaN
this.originalBackgroundWidth > newWidth)
{
newWidth = this.originalBackgroundWidth;
}
}
if(needsHeight)
{
newHeight = this._viewPort.height;
if(this._icon)
{
var iconHeight:Number = this._icon.height;
if(iconHeight === iconHeight) //!isNaN
{
newHeight = Math.max(newHeight, this._icon.height);
}
}
newHeight += this._bottomViewPortOffset + this._topViewPortOffset;
if(this.originalBackgroundHeight === this.originalBackgroundHeight && //!isNaN
this.originalBackgroundHeight > newHeight)
{
newHeight = this.originalBackgroundHeight;
}
}
this.header.width = oldHeaderWidth;
this.header.height = oldHeaderHeight;
if(this.footer)
{
this.footer.width = oldFooterWidth;
this.footer.height = oldFooterHeight;
}
this._ignoreHeaderResizing = oldIgnoreHeaderResizing;
this._ignoreFooterResizing = oldIgnoreFooterResizing;
return this.setSizeInternal(newWidth, newHeight, false);
}
/**
* Creates and adds the header
sub-component and
* removes the old instance, if one exists.
*
* Meant for internal use, and subclasses may override this function
* with a custom implementation.
*
* @see #header
* @see #headerFactory
* @see #customHeaderStyleName
*/
override protected function createHeader():void
{
super.createHeader();
this.headerHeader = Header(this.header);
}
/**
* Creates and adds the buttonGroupFooter
sub-component and
* removes the old instance, if one exists.
*
* Meant for internal use, and subclasses may override this function
* with a custom implementation.
*
* @see #buttonGroupFooter
* @see #buttonGroupFactory
* @see #customButtonGroupStyleName
*/
protected function createButtonGroup():void
{
if(this.buttonGroupFooter)
{
this.buttonGroupFooter.removeEventListener(Event.TRIGGERED, buttonsFooter_triggeredHandler);
}
super.createFooter();
this.buttonGroupFooter = ButtonGroup(this.footer);
this.buttonGroupFooter.addEventListener(Event.TRIGGERED, buttonsFooter_triggeredHandler);
}
/**
* @private
*/
override protected function createFooter():void
{
this.createButtonGroup();
}
/**
* Creates and adds the messageTextRenderer
sub-component and
* removes the old instance, if one exists.
*
* Meant for internal use, and subclasses may override this function
* with a custom implementation.
*
* @see #message
* @see #messageTextRenderer
* @see #messageFactory
*/
protected function createMessage():void
{
if(this.messageTextRenderer)
{
this.removeChild(DisplayObject(this.messageTextRenderer), true);
this.messageTextRenderer = null;
}
var factory:Function = this._messageFactory != null ? this._messageFactory : FeathersControl.defaultTextRendererFactory;
this.messageTextRenderer = ITextRenderer(factory());
var messageStyleName:String = this._customMessageStyleName != null ? this._customMessageStyleName : this.messageStyleName;
var uiTextRenderer:IFeathersControl = IFeathersControl(this.messageTextRenderer);
uiTextRenderer.styleNameList.add(messageStyleName);
uiTextRenderer.touchable = false;
this.addChild(DisplayObject(this.messageTextRenderer));
}
/**
* @private
*/
override protected function refreshFooterStyles():void
{
super.refreshFooterStyles();
this.buttonGroupFooter.dataProvider = this._buttonsDataProvider;
}
/**
* @private
*/
protected function refreshMessageStyles():void
{
for(var propertyName:String in this._messageProperties)
{
var propertyValue:Object = this._messageProperties[propertyName];
this.messageTextRenderer[propertyName] = propertyValue;
}
}
/**
* @private
*/
override protected function calculateViewPortOffsets(forceScrollBars:Boolean = false, useActualBounds:Boolean = false):void
{
super.calculateViewPortOffsets(forceScrollBars, useActualBounds);
if(this._icon)
{
if(this._icon is IValidating)
{
IValidating(this._icon).validate();
}
var iconWidth:Number = this._icon.width;
if(iconWidth == iconWidth) //!isNaN
{
this._leftViewPortOffset += this._icon.width + this._gap;
}
}
}
/**
* @private
*/
protected function buttonsFooter_triggeredHandler(event:Event, data:Object):void
{
this.removeFromParent();
this.dispatchEventWith(Event.CLOSE, false, data);
this.dispose();
}
/**
* @private
*/
protected function icon_resizeHandler(event:Event):void
{
this.invalidate(INVALIDATION_FLAG_LAYOUT);
}
}
}