| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package spark.components |
| { |
| |
| import flash.display.DisplayObject; |
| import flash.display.DisplayObjectContainer; |
| import flash.display.InteractiveObject; |
| import flash.events.Event; |
| import flash.events.EventDispatcher; |
| |
| import mx.core.FlexGlobals; |
| import mx.core.IFlexDisplayObject; |
| import mx.core.IMXMLObject; |
| import mx.core.IVisualElement; |
| import mx.core.IVisualElementContainer; |
| import mx.core.UIComponent; |
| import mx.core.mx_internal; |
| import mx.events.FlexEvent; |
| import mx.events.ItemClickEvent; |
| import mx.utils.NameUtil; |
| |
| use namespace mx_internal; |
| |
| //-------------------------------------- |
| // Events |
| //-------------------------------------- |
| |
| /** |
| * Dispatched when the value of the selected RadioButton component in |
| * this group changes. |
| * |
| * @eventType flash.events.Event.CHANGE |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| [Event(name="change", type="flash.events.Event")] |
| |
| /** |
| * Dispatched when a user selects a RadioButton component in the group. |
| * You can also set a handler for individual RadioButton components. |
| * |
| * This event is dispatched only when the |
| * user interacts with the radio buttons by using the mouse. |
| * |
| * @eventType mx.events.ItemClickEvent.ITEM_CLICK |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| [Event(name="itemClick", type="mx.events.ItemClickEvent")] |
| |
| //-------------------------------------- |
| // Validation events |
| //-------------------------------------- |
| |
| /** |
| * Dispatched when values are changed programmatically |
| * or by user interaction. |
| * |
| * <p>Because a programmatic change triggers this event, make sure |
| * that any <code>valueCommit</code> event handler does not change |
| * a value that causes another <code>valueCommit</code> event. |
| * For example, do not change the<code>selectedValue</code> |
| * property or <code>selection</code> property in a <code>valueCommit</code> |
| * event handler. </p> |
| * |
| * @eventType mx.events.FlexEvent.VALUE_COMMIT |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| [Event(name="valueCommit", type="mx.events.FlexEvent")] |
| |
| //-------------------------------------- |
| // Other metadata |
| //-------------------------------------- |
| |
| [IconFile("RadioButtonGroup.png")] |
| [DefaultTriggerEvent("change")] |
| |
| /** |
| * The RadioButtonGroup component defines a group of RadioButton components |
| * that act as a single mutually exclusive component; therefore, |
| * a user can select only one RadioButton component at a time. |
| * The <code>id</code> property is required when you use the |
| * <code><s:RadioButtonGroup></code> tag to define the group name. Any |
| * <code><s:RadioButton></code> component added to this group will |
| * have this group name. |
| * |
| * <p>Notice that the RadioButtonGroup component is a subclass of EventDispatcher, |
| * not UIComponent, and implements the IMXMLObject interface. |
| * All other Flex visual components implement the IVisualElement interface. |
| * The RadioButtonGroup component declaration must |
| * be contained within the <code><Declarations></code> tag since it is |
| * not assignable to IVisualElement.</p> |
| * |
| * <p>To use this component in a list-based component, such as a List or DataGrid, |
| * create an item renderer. |
| * For information about creating an item renderer, see |
| * <a href="http://help.adobe.com/en_US/flex/using/WS4bebcd66a74275c3-fc6548e124e49b51c4-8000.html"> |
| * Custom Spark item renderers</a>. </p> |
| * |
| * @mxml |
| * |
| * <p>The <code><s:RadioButtonGroup></code> tag inherits all of the |
| * tag attributes of its superclass, and adds the following tag attributes:</p> |
| * |
| * <pre> |
| * <s:RadioButtonGroup |
| * <strong>Properties</strong> |
| * enabled="true" |
| * selectedValue="null" |
| * selection="null" |
| * |
| * <strong>Events</strong> |
| * change="<i>No default</i>" |
| * itemClick="<i>No default</i>" |
| * valueCommit="<i>No default</i>" |
| * /> |
| * </pre> |
| * |
| * @see spark.components.RadioButton |
| * @includeExample examples/RadioButtonGroupExample.mxml |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public class RadioButtonGroup extends EventDispatcher implements IMXMLObject |
| { |
| include "../core/Version.as"; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * @param document In simple cases where a class extends EventDispatcher, |
| * the <code>document</code> parameter should not be used. |
| * |
| * @see flash.events.EventDispatcher |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function RadioButtonGroup(document:IFlexDisplayObject = null) |
| { |
| super(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Since there is no id, generate one, if needed. |
| */ |
| private var _name:String; |
| |
| /** |
| * @private |
| * The document containing a reference to this RadioButtonGroup. |
| */ |
| private var document:IFlexDisplayObject; |
| |
| /** |
| * @private |
| * An Array of the RadioButtons that belong to this group. |
| */ |
| private var radioButtons:Array /* of RadioButton */ = []; |
| |
| /** |
| * @private |
| * Whether the group is enabled. This can be different than the individual |
| * radio buttons in the group. |
| */ |
| private var _enabled:Boolean = true; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // enabled |
| //---------------------------------- |
| |
| [Inspectable(category="General", defaultValue="true")] |
| |
| /** |
| * Determines whether selection is allowed. Note that the value returned |
| * only reflects the value that was explicitly set on the |
| * <code>RadioButtonGroup</code> and does not reflect any values explicitly |
| * set on the individual RadioButtons. |
| * |
| * @default true |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get enabled():Boolean |
| { |
| return _enabled; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set enabled(value:Boolean):void |
| { |
| if (_enabled == value) |
| return; |
| |
| _enabled = value; |
| |
| // The group state changed. Invalidate all the radio buttons. The |
| // radio button skin most likely will change. |
| for (var i:int = 0; i < numRadioButtons; i++) |
| getRadioButtonAt(i).invalidateSkinState(); |
| } |
| |
| //---------------------------------- |
| // numRadioButtons |
| //---------------------------------- |
| |
| [Bindable("numRadioButtonsChanged")] |
| |
| /** |
| * The number of RadioButtons that belong to this RadioButtonGroup. |
| * |
| * @default "0" |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get numRadioButtons():int |
| { |
| return radioButtons.length; |
| } |
| |
| |
| //---------------------------------- |
| // selectedIndex |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _selectedIndex:int = -1; |
| |
| [Bindable("change")] |
| [Bindable("valueCommit")] |
| [Inspectable(category="General")] |
| |
| /** |
| * The index of the selected RadioButton component in the group. |
| * If a RadioButton is not selected, this property is <code>-1</code>. |
| * |
| * @default -1 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11.1 |
| * @playerversion AIR 3.4 |
| * @productversion Flex 4.10 |
| */ |
| public function get selectedIndex():int |
| { |
| return _selectedIndex; |
| } |
| |
| /** |
| * @private. |
| */ |
| public function set selectedIndex(newValue:int):void |
| { |
| if (newValue == _selectedIndex) |
| { |
| return; |
| } |
| |
| if (newValue == -1) |
| { |
| setSelection(null, false); |
| dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); |
| |
| return; |
| } |
| |
| changeSelection(newValue, false) |
| |
| dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); |
| } |
| |
| |
| //---------------------------------- |
| // selectedValue |
| //---------------------------------- |
| |
| /** |
| * @private |
| * Storage for the selectedValue property. |
| */ |
| private var _selectedValue:Object; |
| |
| [Bindable("change")] |
| [Bindable("valueCommit")] |
| [Inspectable(category="General")] |
| |
| /** |
| * The <code>value</code> property of the selected |
| * RadioButton component in the group, if it has been set, |
| * otherwise, the <code>label</code> property of the selected RadioButton. |
| * If no RadioButton is selected, this property is <code>null</code>. |
| * |
| * <p>If you set <code>selectedValue</code>, Flex selects the |
| * first RadioButton component whose <code>value</code> or |
| * <code>label</code> property matches this value.</p> |
| * |
| * @default null |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get selectedValue():Object |
| { |
| if (selection) |
| { |
| return selection.value != null ? |
| selection.value : |
| selection.label; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * @private. |
| */ |
| public function set selectedValue(value:Object):void |
| { |
| // The rbg might set the selectedValue before the radio buttons are |
| // initialized and inserted in the group. This will hold the selected |
| // value until it can be put in the group. |
| _selectedValue = value; |
| |
| // Clear the exisiting selecton if there is one. |
| if (value == null) |
| { |
| setSelection(null, false); |
| return; |
| } |
| |
| // Find the radio button value specified. |
| var n:int = numRadioButtons; |
| for (var i:int = 0; i < n; i++) |
| { |
| var radioButton:RadioButton = getRadioButtonAt(i); |
| if (radioButton.value == value || |
| radioButton.label == value) |
| { |
| changeSelection(i, false); |
| _selectedValue = null; |
| |
| dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); |
| |
| break; |
| } |
| } |
| } |
| |
| //---------------------------------- |
| // selection |
| //---------------------------------- |
| |
| /** |
| * @private |
| * Reference to the selected radio button. |
| */ |
| private var _selection:RadioButton; |
| |
| [Bindable("change")] |
| [Bindable("valueCommit")] |
| [Inspectable(category="General")] |
| |
| /** |
| * Contains a reference to the currently selected |
| * RadioButton component in the group. |
| * You can access this property in ActionScript only; |
| * it is not settable in MXML. |
| * Setting this property to <code>null</code> deselects the currently |
| * selected RadioButton component. A change event is not dispatched. |
| * |
| * @default null |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get selection():RadioButton |
| { |
| return _selection; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set selection(value:RadioButton):void |
| { |
| if ( _selection == value) |
| return; |
| |
| // Going through the selection setter should never fire a change event. |
| setSelection(value, false); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Implementation of the <code>IMXMLObject.initialized()</code> method |
| * to support deferred instantiation. |
| * |
| * @param document The MXML document that created this object. |
| * |
| * @param id The identifier used by document to refer to this object. |
| * If the object is a deep property on document, <code>id</code> is null. |
| * |
| * @see mx.core.IMXMLObject |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function initialized(document:Object, id:String):void |
| { |
| _name = id; |
| |
| this.document = document ? |
| IFlexDisplayObject(document) : |
| IFlexDisplayObject(FlexGlobals.topLevelApplication); |
| } |
| |
| /** |
| * Returns the RadioButton component at the specified index. |
| * |
| * @param index The 0-based index of the RadioButton in the |
| * RadioButtonGroup. |
| * |
| * @return The specified RadioButton component if index is between |
| * 0 and <code>numRadioButtons</code> - 1. Returns |
| * <code>null</code> if the index is invalid. |
| * |
| * @see numRadioButtons |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function getRadioButtonAt(index:int):RadioButton |
| { |
| if (index >= 0 && index < numRadioButtons) |
| return radioButtons[index]; |
| |
| return null; |
| } |
| |
| /** |
| * @private |
| * String to uniquely identify this radio button group. |
| */ |
| mx_internal function get name():String |
| { |
| if (_name == null) |
| _name = NameUtil.createUniqueName(this); |
| |
| return _name; |
| } |
| |
| /** |
| * @private |
| * Add a radio button to the group. This can be called by |
| * RadioButton or via the addedHandler when applying a state. |
| */ |
| mx_internal function addInstance(instance:RadioButton):void |
| { |
| // During a state transition, called when rb is removed from |
| // display list. |
| instance.addEventListener(Event.REMOVED, radioButton_removedHandler); |
| |
| radioButtons.push(instance); |
| |
| // Apply group indices in "tab order" or "breadth-first" order. |
| radioButtons.sort(readOrderCompare); |
| for (var i:int = 0; i < radioButtons.length; i++) |
| radioButtons[i].indexNumber = i; |
| |
| // There is a pending selectedValue. See if we can set it now. |
| if (_selectedValue != null) |
| selectedValue = _selectedValue; |
| |
| // If this radio button is selected, then it becomes the selection |
| // for the group. |
| if (instance.selected == true) |
| selection = instance; |
| |
| instance.radioButtonGroup = this; |
| instance.invalidateSkinState(); |
| |
| dispatchEvent(new Event("numRadioButtonsChanged")); |
| } |
| |
| /** |
| * @private |
| * Remove a radio button from the group. This can be called by |
| * RadioButton or via the removedHandler when removing a state. |
| */ |
| private function removeInstance(instance:RadioButton):void |
| { |
| if (instance) |
| { |
| var foundInstance:Boolean = false; |
| for (var i:int = 0; i < numRadioButtons; i++) |
| { |
| var rb:RadioButton = getRadioButtonAt(i); |
| |
| if (foundInstance) |
| { |
| // Decrement the indexNumber for each button after the removed button. |
| rb.indexNumber = rb.indexNumber - 1; |
| } |
| else if (rb == instance) |
| { |
| // During a state transition, called when rb is added back |
| // to display list. |
| instance.addEventListener(Event.ADDED, radioButton_addedHandler); |
| |
| // Don't set the group to null. If this is being removed |
| // because the state changed, the group will be needed |
| // if the radio button is readded later because of another |
| // state transition. |
| //rb.group = null; |
| |
| // If the rb is selected, leave the button itself selected |
| // but clear the selection for the group. |
| if (instance == _selection) |
| _selection = null; |
| |
| instance.radioButtonGroup = null; |
| instance.invalidateSkinState(); |
| |
| // Remove the radio button from the internal array. |
| radioButtons.splice(i,1); |
| foundInstance = true; |
| |
| // redo the same index because we removed the previous item at this index |
| i--; |
| } |
| } |
| |
| if (foundInstance) |
| dispatchEvent(new Event("numRadioButtonsChanged")); |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function setSelection(value:RadioButton, fireChange:Boolean = true):void |
| { |
| if (_selection == value) |
| return; |
| |
| if (value == null) |
| { |
| if (selection != null) |
| { |
| _selection.selected = false; |
| _selection = null; |
| _selectedIndex = -1; |
| if (fireChange) |
| dispatchEvent(new Event(Event.CHANGE)); |
| } |
| } |
| else |
| { |
| var n:int = numRadioButtons; |
| for (var i:int = 0; i < n; i++) |
| { |
| if (value == getRadioButtonAt(i)) |
| { |
| changeSelection(i, fireChange); |
| break; |
| } |
| } |
| } |
| |
| dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); |
| } |
| |
| /** |
| * @private |
| */ |
| private function changeSelection(index:int, fireChange:Boolean = true):void |
| { |
| var rb:RadioButton = getRadioButtonAt(index); |
| if (rb && rb != _selection) |
| { |
| // Unselect the currently selected radio |
| if (_selection) |
| _selection.selected = false; |
| |
| // Change the focus to the new radio. |
| // Set the state of the new radio to true. |
| // Fire a click event for the new radio. |
| // Fire a click event for the radio group. |
| _selection = rb; |
| _selection.selected = true; |
| _selectedIndex = index; |
| if (fireChange) |
| dispatchEvent(new Event(Event.CHANGE)); |
| } |
| } |
| |
| /** |
| * @private |
| * Sandbox root of RadioButton "a" in readOrderCompare(). |
| */ |
| private var aSbRoot:DisplayObject; |
| |
| /** |
| * @private |
| * Sandbox root of RadioButton "b" in readOrderCompare(). |
| */ |
| private var bSbRoot:DisplayObject; |
| |
| /** |
| * @private |
| * Comparison function used to sort items as they are added to the radioButtons array. |
| * Compares by tabIndex or if neither item defines a tabIndex, by "breadthOrder." |
| * |
| * Returns -1 if a is before b in sort order, 0 if a and b have same |
| * sort order and 1 if a after b in sort order. |
| */ |
| private function readOrderCompare(a:DisplayObject, b:DisplayObject):Number |
| { |
| var aParent:DisplayObjectContainer = a.parent; |
| var bParent:DisplayObjectContainer = b.parent; |
| |
| if (!aParent || !bParent) |
| return 0; |
| |
| // Only set when a is the radio button. The sandbox root should be the |
| // same for the parents. |
| if (a is RadioButton) |
| aSbRoot = RadioButton(a).systemManager.getSandboxRoot(); |
| |
| // Only set when b is the radio button. The sandbox root should be the |
| // same for the parents. |
| if (b is RadioButton) |
| bSbRoot = RadioButton(b).systemManager.getSandboxRoot(); |
| |
| // If reached the sandbox root of either then done. |
| if (aParent == aSbRoot || bParent == bSbRoot) |
| return 0; |
| |
| // first check to see if we can compare by tabIndex |
| var aTabIndex:int = (a is InteractiveObject) ? InteractiveObject(a).tabIndex : -1; |
| var bTabIndex:int = (b is InteractiveObject) ? InteractiveObject(b).tabIndex : -1; |
| |
| // if one of the items being compared has a defined tabIndex, compare by tabIndex |
| if(aTabIndex > -1 || bTabIndex > -1) |
| { |
| if (aTabIndex > bTabIndex) |
| return (bTabIndex == -1) ? -1 : 1; // items with assigned tabIndex come before those without |
| if (aTabIndex < bTabIndex) |
| return (aTabIndex == -1) ? 1 : -1; // items without assigned tabIndex come after those without |
| if (a == b) |
| return 0; |
| } |
| |
| // if neither item has a defined tabIndex, compare by "breadthOrder" |
| var aNestLevel:int = (a is UIComponent) ? UIComponent(a).nestLevel : -1; |
| var bNestLevel:int = (b is UIComponent) ? UIComponent(b).nestLevel : -1; |
| |
| var aIndex:int = 0; |
| var bIndex:int = 0; |
| |
| if (aParent == bParent) |
| { |
| if (aParent is IVisualElementContainer && a is IVisualElement) |
| aIndex = IVisualElementContainer(aParent).getElementIndex(IVisualElement(a)); |
| else |
| aIndex = DisplayObjectContainer(aParent).getChildIndex(a); |
| |
| if (bParent is IVisualElementContainer && b is IVisualElement) |
| bIndex = IVisualElementContainer(bParent).getElementIndex(IVisualElement(b)); |
| else |
| bIndex = DisplayObjectContainer(bParent).getChildIndex(b); |
| } |
| |
| if (aNestLevel > bNestLevel || aIndex > bIndex) |
| return 1; |
| else if (aNestLevel < bNestLevel || bIndex > aIndex) |
| return -1; |
| else if (a == b) |
| return 0; |
| else // Nest levels are identical, compare ancestors. |
| return readOrderCompare(aParent, bParent); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Event Handlers |
| // |
| //-------------------------------------------------------------------------- |
| /** |
| * @private |
| * Called during a state transition when rb is added back to display list. |
| */ |
| private function radioButton_addedHandler(event:Event):void |
| { |
| var rb:RadioButton = event.target as RadioButton; |
| if (rb) |
| { |
| //trace("radioButton_addedHandler", rb.id); |
| rb.removeEventListener(Event.ADDED, radioButton_addedHandler); |
| addInstance(rb); |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| private function radioButton_removedHandler(event:Event):void |
| { |
| var rb:RadioButton = event.target as RadioButton; |
| if (rb) |
| { |
| //trace("radioButton_removedHandler", rb.id); |
| rb.removeEventListener(Event.REMOVED, radioButton_removedHandler); |
| removeInstance(rb); |
| } |
| } |
| } |
| |
| } |