| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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 mx.controls |
| { |
| |
| 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.IRawChildrenContainer; |
| import mx.core.UIComponent; |
| import mx.core.mx_internal; |
| import mx.events.FlexEvent; |
| import mx.events.ItemClickEvent; |
| |
| use namespace mx_internal; |
| |
| //-------------------------------------- |
| // Events |
| //-------------------------------------- |
| |
| /** |
| * Dispatched when the value of the selected RadioButton control in |
| * this group changes. |
| * |
| * @eventType flash.events.Event.CHANGE |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Event(name="change", type="flash.events.Event")] |
| |
| /** |
| * Dispatched when a user selects a RadioButton control in the group. |
| * You can also set a handler for individual RadioButton controls. |
| * |
| * @eventType mx.events.ItemClickEvent.ITEM_CLICK |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Event(name="itemClick", type="mx.events.ItemClickEvent")] |
| |
| //-------------------------------------- |
| // Other metadata |
| //-------------------------------------- |
| |
| [DefaultBindingProperty(source="selection", destination="selection")] |
| |
| [DefaultTriggerEvent("change")] |
| |
| [IconFile("RadioButtonGroup.png")] |
| |
| [Alternative(replacement="spark.components.RadioButtonGroup", since="4.0")] |
| |
| /** |
| * The RadioButtonGroup control defines a group of RadioButton controls |
| * that act as a single mutually exclusive control; therefore, |
| * a user can select only one RadioButton control at a time. While grouping |
| * RadioButton instances |
| * in a RadioButtonGroup is optional, a group lets you do things |
| * like set a single event handler on a group of buttons, rather than |
| * on each individual button. |
| * |
| * <p>The <code>id</code> property is required when you use the |
| * <code><mx:RadioButtonGroup></code> tag to define the name |
| * of the group.</p> |
| * |
| * <p>Notice that the RadioButtonGroup control is a subclass of EventDispatcher, not UIComponent, |
| * and implements the IMXMLObject interface. |
| * All other Flex visual components are subclasses of UIComponent, which implements |
| * the IUIComponent interface. |
| * The RadioButtonGroup control has support built into the Flex compiler |
| * that allows you to use the RadioButtonGroup control as a child of a Flex container, |
| * even though it does not implement IUIComponent. |
| * All other container children must implement the IUIComponent interface.</p> |
| * |
| * <p>Therefore, if you try to define a visual component as a subclass of |
| * EventDispatcher that implements the IMXMLObject interface, |
| * you will not be able to use it as the child of a container.</p> |
| * |
| * @mxml |
| * |
| * <p>The <code><mx:RadioButtonGroup></code> tag inherits all of the |
| * tag attributes of its superclass, and adds the following tag attributes:</p> |
| * |
| * <pre> |
| * <mx:RadioButtonGroup |
| * <strong>Properties</strong> |
| * enabled="true|false" |
| * id="<i>No default</i>" |
| * labelPlacement="right|left|top|bottom" |
| * |
| * <strong>Events</strong> |
| * change="<i>No default</i>" |
| * itemClick="<i>No default</i>" |
| * /> |
| * </pre> |
| * |
| * @includeExample examples/RadioButtonGroupExample.mxml |
| * |
| * @see mx.controls.RadioButton |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| 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 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function RadioButtonGroup(document:IFlexDisplayObject = null) |
| { |
| super(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @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 */ = []; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // enabled |
| //---------------------------------- |
| |
| [Bindable("enabledChanged")] |
| [Inspectable(category="General", defaultValue="true")] |
| |
| /** |
| * Determines whether selection is allowed. |
| * |
| * @default true |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get enabled():Boolean |
| { |
| var s:Number = 0; |
| |
| var n:int = numRadioButtons; |
| for (var i:int = 0; i < n; i++) |
| { |
| s = s + getRadioButtonAt(i).enabled; |
| } |
| |
| if (s == 0) |
| return false; |
| |
| if (s == n) |
| return true; |
| |
| return false; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set enabled(value:Boolean):void |
| { |
| var n:int = numRadioButtons; |
| for (var i:int = 0; i < n; i++) |
| { |
| getRadioButtonAt(i).enabled = value; |
| } |
| |
| dispatchEvent(new Event("enabledChanged")); |
| } |
| |
| //---------------------------------- |
| // labelPlacement |
| //---------------------------------- |
| |
| /** |
| * @private |
| * Storage for the labelPlacement property. |
| */ |
| private var _labelPlacement:String = "right"; |
| |
| [Bindable("labelPlacementChanged")] |
| [Inspectable(category="General", enumeration="left,right,top,bottom", defaultValue="right")] |
| |
| /** |
| * Position of the RadioButton label relative to the RadioButton icon |
| * for each control in the group. |
| * You can override this setting for the individual controls. |
| * |
| * <p>Valid values in MXML are <code>"right"</code>, <code>"left"</code>, |
| * <code>"bottom"</code>, and <code>"top"</code>. </p> |
| * |
| * <p>In ActionScript, you use the following constants to set this property: |
| * <code>ButtonLabelPlacement.RIGHT</code>, <code>ButtonLabelPlacement.LEFT</code>, |
| * <code>ButtonLabelPlacement.BOTTOM</code>, and <code>ButtonLabelPlacement.TOP</code>.</p> |
| * |
| * @default "right" |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get labelPlacement():String |
| { |
| return _labelPlacement; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set labelPlacement(value:String):void |
| { |
| _labelPlacement = value; |
| |
| var n:int = numRadioButtons; |
| for (var i:int = 0; i < n; i++) |
| { |
| getRadioButtonAt(i).labelPlacement = value; |
| } |
| } |
| |
| //---------------------------------- |
| // numRadioButtons |
| //---------------------------------- |
| |
| [Bindable("numRadioButtonsChanged")] |
| |
| /** |
| * The number of RadioButtons that belong to this RadioButtonGroup. |
| * |
| * @default "undefined" |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get numRadioButtons():int |
| { |
| return radioButtons.length; |
| } |
| |
| //---------------------------------- |
| // selectedValue |
| //---------------------------------- |
| |
| /** |
| * @private |
| * Storage for the selectedValue property. |
| */ |
| private var _selectedValue:Object; |
| |
| [Bindable("change")] |
| [Bindable("valueCommit")] |
| [Inspectable(category="General")] |
| |
| /** |
| * The value of the <code>value</code> property of the selected |
| * RadioButton control in the group, if this has been set |
| * to be something other than <code>null</code> (the default value). |
| * Otherwise, <code>selectedValue</code> is the value of 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 |
| * RadioButton control whose <code>value</code> or |
| * <code>label</code> property matches this value.</p> |
| * |
| * @default null |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| 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 |
| { |
| _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); |
| break; |
| } |
| } |
| |
| dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); |
| } |
| |
| //---------------------------------- |
| // 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 control in the group. |
| * You can access the property in ActionScript only; |
| * it is not settable in MXML. |
| * Setting this property to <code>null</code> deselects the currently selected RadioButton control. |
| * |
| * @default null |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get selection():RadioButton |
| { |
| return _selection; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set selection(value:RadioButton):void |
| { |
| // 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 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function initialized(document:Object, id:String):void |
| { |
| this.document = document ? |
| IFlexDisplayObject(document) : |
| IFlexDisplayObject(FlexGlobals.topLevelApplication); |
| } |
| |
| /** |
| * Returns the RadioButton control at the specified index. |
| * |
| * @param index The index of the RadioButton control in the |
| * RadioButtonGroup control, where the index of the first control is 0. |
| * |
| * @return The specified RadioButton control. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function getRadioButtonAt(index:int):RadioButton |
| { |
| return RadioButton(radioButtons[index]); |
| } |
| |
| /** |
| * @private |
| * Add a radio button to the group. |
| */ |
| mx_internal function addInstance(instance:RadioButton):void |
| { |
| 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; |
| |
| if (_selectedValue != null) |
| selectedValue = _selectedValue; |
| |
| dispatchEvent(new Event("numRadioButtonsChanged")); |
| } |
| |
| /** |
| * @private |
| * Remove a radio button from the group. |
| */ |
| mx_internal 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--; |
| } |
| else if (rb == instance) |
| { |
| rb.group = null; |
| |
| if (instance == _selection) |
| { |
| _selection = null; |
| } |
| // 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 |
| * Return the value or the label value |
| * of the selected radio button. |
| */ |
| private function getValue():String |
| { |
| if (selection) |
| { |
| return selection.value && |
| selection.value is String && |
| String(selection.value).length != 0 ? |
| String(selection.value) : |
| selection.label; |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function setSelection(value:RadioButton, fireChange:Boolean = true):void |
| { |
| if (value == null) |
| { |
| if (selection != null) |
| { |
| _selection.selected = false; |
| _selection = null; |
| 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 |
| { |
| if (getRadioButtonAt(index)) |
| { |
| // 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 = getRadioButtonAt(index); |
| _selection.selected = true; |
| if (fireChange) |
| dispatchEvent(new Event(Event.CHANGE)); |
| } |
| } |
| |
| /** |
| * @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:DisplayObject = (a is UIComponent) ? UIComponent(a).$parent : a.parent; |
| var bParent:DisplayObject = (b is UIComponent) ? UIComponent(b).$parent : b.parent; |
| |
| if (!aParent || !bParent) |
| 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 we 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 = DisplayObjectContainer(aParent).getChildIndex(a); |
| |
| var bIndex:int = DisplayObjectContainer(bParent).getChildIndex(b); |
| |
| if (aNestLevel > bNestLevel || (a.parent == b.parent && aIndex > bIndex)) |
| return 1; |
| else if (aNestLevel < bNestLevel || (a.parent == b.parent && bIndex > aIndex)) |
| return -1; |
| else if (a == b) |
| return 0; |
| else // Nest levels are identical, compare ancestors. |
| return readOrderCompare(aParent, bParent); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Event Handlers |
| // |
| //-------------------------------------------------------------------------- |
| /** |
| * @private |
| */ |
| private function radioButton_removedHandler(event:Event):void |
| { |
| var rb:RadioButton = event.target as RadioButton; |
| if (rb) |
| { |
| rb.removeEventListener(Event.REMOVED, radioButton_removedHandler); |
| removeInstance(RadioButton(event.target)); |
| } |
| } |
| } |
| |
| } |