blob: 54c7076e98c0ccc734cbfef5b19651710e74bbe8 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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 org.apache.royale.html.beads
{
import org.apache.royale.core.IBead;
import org.apache.royale.core.IChild;
import org.apache.royale.core.IStrand;
import org.apache.royale.core.IParentIUIBase;
import org.apache.royale.events.ValueEvent;
import org.apache.royale.core.IParent;
import org.apache.royale.core.ISelectable;
import org.apache.royale.events.Event;
import org.apache.royale.events.IEventDispatcher;
import org.apache.royale.utils.array.rangeCheck;
import org.apache.royale.events.EventDispatcher;
import org.apache.royale.debugging.assert;
/**
* The change event is dispatched whenever the selection changes.
*
* @langversion 3.0
* @playerversion Flash 10.2
* @playerversion AIR 2.6
* @productversion Royale 0.9
*/
[Event(name="change", type="org.apache.royale.events.Event")]
/**
* Use SingleSelectionContainerBead is a bead which manages selection state of a component
* which contains a group of ISelectables. One such example would be a toggle button bar.
*
* Only one of the elements can be selected at any one time.
*
* @langversion 3.0
* @playerversion Flash 10.2
* @playerversion AIR 2.6
* @productversion Royale 0.9
*/
public class SingleSelectionContainerBead extends EventDispatcher implements IBead
{
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 10.2
* @playerversion AIR 2.6
* @productversion Royale 0.9
*/
public function SingleSelectionContainerBead()
{
super();
}
protected var _strand:IStrand;
private function get host():IParentIUIBase
{
return _strand as IParentIUIBase;
}
/**
* @copy org.apache.royale.core.IBead#strand
*
* @langversion 3.0
* @playerversion Flash 10.2
* @playerversion AIR 2.6
* @productversion Royale 0.9
*/
public function set strand(value:IStrand):void
{
_strand = value;
_elements = [];
host.addEventListener("childrenAdded",handleItemAdded);
host.addEventListener("childrenRemoved",handleItemRemoved);
addListenersToChildren();
}
private function addListenersToChildren():void
{
var parent:IParent = _strand as IParent;
var numElements:int = parent.numElements;
_elements = [];
for(var i:int = 0; i < numElements ; i++)
{
var elem:ISelectable = parent.getElementAt(i) as ISelectable;
if(elem)
{
(elem as IEventDispatcher).addEventListener("change",handleChange);
_elements.push(elem);
}
}
normalizeSelection();
}
private var _selectedIndex:int = -1;
/**
* The index of the currently selected item. Changing this value
* also changes the selectedItem property.
*
* @langversion 3.0
* @playerversion Flash 10.2
* @playerversion AIR 2.6
* @productversion Royale 0.0
*/
[Bindable("change")]
public function get selectedIndex():int
{
return rangeCheck(_selectedIndex,_elements) ? _selectedIndex : -1;
}
public function set selectedIndex(value:int):void
{
// The selectedIndex can be set before it's added to the strand (i.e. in mxml)
if(!_elements)
{
_selectedIndex = value;
return;
}
assert(!_selectionRequired || rangeCheck(value,_elements), "When selectionRequired is true, a valid index must be set");
assert(value < _elements.length,"index is out of range");
if(value < 0)
{
_selectedIndex = -1;
normalizeSelection();
}
else if(value != _selectedIndex)
{
var elem:ISelectable = _elements[value];
elem.selected = true;
}
}
/**
* The item currently selected. Changing this value also
* changes the selectedIndex property.
*
* @langversion 3.0
* @playerversion Flash 10.2
* @playerversion AIR 2.6
* @productversion Royale 0.0
*/
[Bindable("change")]
public function get selectedItem():ISelectable
{
// var selection:ISelectable;
// if(rangeCheck(_selectedIndex,_elements))
// return _elements[_selectedIndex];
// normalizeSelection();
return rangeCheck(_selectedIndex,_elements) ? _elements[_selectedIndex] : null;
}
public function set selectedItem(value:ISelectable):void
{
var idx:int = _elements.indexOf(value);
assert(idx != -1,"The specified element is not valid");
if(idx != _selectedIndex)
value.selected = true;
}
/**
* If selectionRequired is true, one element is always selected
* and clicking on a selected element does not deselect it.
*
* @langversion 3.0
* @playerversion Flash 10.2
* @playerversion AIR 2.6
* @productversion Royale 0.9
*/
private var _selectionRequired:Boolean = true;
public function get selectionRequired():Boolean
{
return _selectionRequired;
}
public function set selectionRequired(value:Boolean):void
{
_selectionRequired = value;
}
private var _elements:Array;
private var _handlingChange:Boolean;
/**
* @royaleignorecoercion org.apache.royale.events.IEventDispatcher
*/
private function handleChange(event:Event):void
{
//prevent nested events
if(_handlingChange)
return;
_handlingChange = true;
_selectedIndex = -1;
var component:IParentIUIBase = event.target as IParentIUIBase;
var elem:ISelectable = component as ISelectable;
var isSelected:Boolean = elem.selected;
var parent:IParent = _strand as IParent;
var numElements:int = _elements.length;
for(var i:int = 0; i < numElements ; i++)
{
elem = _elements[i] as ISelectable;
if(elem)
{
if(elem == component)
{
if(selectionRequired)
elem.selected = true;
if(elem.selected)
_selectedIndex = i;
}
else if(isSelected)
elem.selected = false;
}
}
_handlingChange = false;
dispatchEvent(new Event("change"));
}
/**
* @royaleignorecoercion org.apache.royale.events.IEventDispatcher
*/
private function handleItemAdded(ev:ValueEvent):void
{
if(ev.value)
{
var elem:ISelectable = ev.value as ISelectable;
if(elem)
{
(elem as IEventDispatcher).addEventListener("change",handleChange);
normalizeElements();
if(elem.selected)
selectedItem = elem;
else
normalizeSelection();
}
}
else
addListenersToChildren();
}
/**
* @royaleignorecoercion org.apache.royale.events.IEventDispatcher
*/
private function handleItemRemoved(ev:ValueEvent):void
{
(ev.value as IEventDispatcher).removeEventListener("change",handleChange);
normalizeElements();
normalizeSelection();
}
private function normalizeElements():void
{
var i:int;
_elements = [];
COMPILE::SWF
{
}
COMPILE::JS
{
// optimized so we don't have to get an array each step of the loop.
var internalChildren:Array = host.internalChildren();
for(i = 0; i < internalChildren.length; i++){
if(internalChildren[i] is ISelectable)
_elements.push(internalChildren[i]);
}
}
}
private function normalizeSelection():void
{
var idx:int = -1;
var i:int;
var elem:ISelectable;
for(i=0;i<_elements.length;i++)
{
elem = _elements[i];
if(idx != -1)
elem.selected = false;
else if(elem.selected)
{
idx = _selectedIndex = i;
}
}
if(idx == -1 && _selectionRequired && _elements.length)
{
if(rangeCheck(_selectedIndex,_elements))
{
elem = _elements[_selectedIndex];
elem.selected = true;
}
else
selectedIndex = 0;
}
}
}
}