0
votes

Is it possible to make an item in a List control not selectable? If so, how would this be accomplished?

I've tried one thing so far. What I did was use a custom item renderer that checks for a value in the data property upon a FlexEvent.DATA_CHANGE event. If that value is not set, I tried setting the item renderer's selectable property to false. This, unfortunately, does not seem to work.

Any ideas?

5

5 Answers

1
votes

So I came upon a solution of my own. Its similar to yours, and seems to do the trick and cover all the bases apart from hacking the page up and page down keys. I say hack, because I'm not sure it handles the increase or decrease in the caretIndex the same way as the List control. Basically it just manually sets the caretIndex to an index before what the next selectable item is and changes the keycode to a simple up or down.

protected function disabledFilterFunction( data:Object ):Boolean
{
    return ( data != null && data.data == null );
}

override protected function mouseEventToItemRenderer( event:MouseEvent ):IListItemRenderer
{
    var item:IListItemRenderer = super.mouseEventToItemRenderer( event );

    if( item && item.data && disabledFilterFunction( item.data ) )
        return null;

    return item;
}

override protected function moveSelectionVertically( code:uint, shiftKey:Boolean, ctrlKey:Boolean ):void
{
    var i:int;
    var newIndex:int;

    switch( code )
    {
        case Keyboard.UP:
            newIndex = getPreviousUnselectableIndex( caretIndex - 1 );
            break;

        case Keyboard.DOWN:
            newIndex = getNextUnselectableIndex( caretIndex + 1 );
            break;

        case Keyboard.HOME:
            newIndex = getFirstSelectableIndex();
            code = Keyboard.UP;
            break;

        case Keyboard.END:
            newIndex = getLastSelectableIndex();
            code = Keyboard.DOWN;
            break;

        case Keyboard.PAGE_UP:
        {
            newIndex = Math.max( getFirstSelectableIndex(), getPreviousUnselectableIndex( caretIndex - ( rowCount - 2 ) ) );
            code = Keyboard.UP;
            break;
        }

        case Keyboard.PAGE_DOWN:
        {
            newIndex = Math.min( getLastSelectableIndex(), getNextUnselectableIndex( caretIndex + ( rowCount - 1 ) ) );
            code = Keyboard.DOWN;
            break;
        }
    }

    if( newIndex > -1 && newIndex < collection.length )
    {
        caretIndex = newIndex;
        super.moveSelectionVertically( code, shiftKey, ctrlKey );
    }
}

private function getFirstSelectableIndex():int
{
    var result:int = -1;

    for( var i:int = 0; i < collection.length; i++ )
    {
        if( !disabledFilterFunction( collection[i] ) )
        {
            result = i + 1;
            break;
        }
    }

    return result;
}

private function getLastSelectableIndex():int
{
    var result:int = -1;

    for( var i:int = collection.length - 1; i > -1; i-- )
    {
        if( !disabledFilterFunction( collection[i] ) )
        {
            result = i - 1;
            break;
        }
    }

    return result;
}

private function getPreviousUnselectableIndex( startIndex:int ):int
{
    var result:int = -1;

    for( var i:int = startIndex; i > -1; i-- )
    {
        if( !disabledFilterFunction( collection[i] ) )
        {
            result = i + 1;
            break;
        }
    }

    return result;
}

private function getNextUnselectableIndex( startIndex:int ):int
{
    var result:int = collection.length;

    for( var i:int = startIndex; i < collection.length; i++ )
    {
        if( !disabledFilterFunction( collection[i] ) )
        {
            result = i - 1;
            break;
        }
    }

    return result;
}
1
votes

I was able to fix the problem of first/last items being non-selectable by simply doing this verticalScrollPosition++ and verticalScrollPosition-- right before caretIndex++ and caretIndex-- respectively (in the example that Michael linked to up above) . I couldn't believe that the fix was so easy, but it was!

0
votes

I was able to do this to add a separator component, following this ComboBox example. Here's an example with the renderer logic stripped out and the selectability logic left in:

package com.example.ui  {
import flash.events.MouseEvent;
import flash.ui.Keyboard;

import mx.controls.List;
import mx.controls.listClasses.IListItemRenderer;

public class MyList extends List
{
    public function MyList()
    {
        super();
    }


    /** Override mouse navigation */
    protected override function mouseEventToItemRenderer(event:MouseEvent):IListItemRenderer {
        var row:IListItemRenderer = super.mouseEventToItemRenderer(event);

        if (row != null && isSelectable(row.data)) {
            return null;
        }
        return row;
    }

    /** Override keyboard navigation */
    protected override function moveSelectionVertically(code:uint, shiftKey:Boolean, ctrlKey:Boolean):void {
        super.moveSelectionVertically(code, shiftKey, ctrlKey);

        if (code == Keyboard.DOWN && isSeparatorData(selectedItem)) {
            caretIndex++;
        }
        if (code == Keyboard.UP && isSeparatorData(selectedItem)) {
            caretIndex--;
        }
        finishKeySelection();           
    }

    /**
     * Define this mechanism in a way that makes sense for your project.
     */
    protected function isSelectable(data:Object):Boolean {
        return data != null && data.hasOwnProperty("type") && data.type == "separator";
    }

}
}

An alternative (still imperfect) that deals better with scrollable lists and consecutive separators:

    protected override function moveSelectionVertically(code:uint, shiftKey:Boolean, ctrlKey:Boolean):void {
        super.moveSelectionVertically(code, shiftKey, ctrlKey);

        var newCode:uint = singleLineCode(code);
        var item:Object = selectedItem;
        var itemChanged:Boolean = true;

        while (!isNaN(newCode) && itemChanged && isSeparatorData(item)) {
            super.moveSelectionVertically(newCode, shiftKey, ctrlKey);
            itemChanged = (item === selectedItem);
            item = selectedItem;
        }
    }

    private function singleLineCode(code:uint):uint {
        switch (code) {
        case Keyboard.UP:
        case Keyboard.PAGE_UP:
            return Keyboard.UP;
            break;
        case Keyboard.DOWN:
        case Keyboard.PAGE_DOWN:
            return Keyboard.DOWN;
            break;
        default:
            return NaN;
            break;                              
        }
        return code;
    }
0
votes

Just thought I'd add my two sense. I was wondering the same thing (how to I set a list to not selectable) and I realized that the spark component datagroup would do exactly that. of course you need to be using flex 4, but if you are, and are wondering can I set my list to not selectable, I'd suggest using the datagroup.

0
votes

I was looking for a solution myself and here's the solution I came up with. Note that I'm using a spark list. I hope someone finds this helpful.

  1. Implement both event handlers for change and changing
  2. Set the selection to -1 and requireSelection to false so that nothing is selected
  3. When you build your dataprovider, enable/disable items as desired
  4. Provide some logic in the changing handler to call 'preventDefault' on the event if it isn't enabled, or shouldn't be selected.

Example: cribbed from my implementation where I build my own items and use a Tile Layout

<s:List id="myListView"
    itemRenderer="spark.skins.spark.DefaultComplexItemRenderer"
    horizontalCenter="0"
    verticalCenter="0"
    borderVisible="false"
    dataProvider="{myItems}"
    change="changeHandler(event)" changing="changingHandler(event)"
    requireSelection="false"
    selectedIndex="-1" >
    <s:layout>
        <s:TileLayout verticalGap="0" />
    </s:layout>
</s:List>
<fx:script>
    <![CDATA[
        import mx.collections.ArrayCollection;            
        import spark.events.IndexChangeEvent;

        [Bindable]
        public var myItems = new ArrayCollection;


        protected function startup():void {
            // Here's where you'd build up your items if they
            // need to be built dynamically.
        }

        protected function changeHandler(event:IndexChangeEvent):void
        {
            var currentIndx:int = event.currentTarget.selectedIndex;
            var selectedItem:UIComponent = event.currentTarget.selectedItem as UIComponent;

            // Do whatever you need to do on selection here
        }

        protected function canMicrophoneChange(event:IndexChangeEvent):void
        {
            var currentIndx:int = event.currentTarget.selectedIndex;
            var selectedItem:UIComponent = event.currentTarget.selectedItem as UIComponent;

            // This will cancel the select if the item was not enabled.
            if (selectedItem.enabled == false) event.preventDefault();
        }
    ]]>
</fx:script>