Question

The proposed UX I am trying to achieve is this:

  1. user clicks menu item (via a listBase subclass: e.g. ButtonBar or TabBar)
  2. prevent initial selection
  3. validate if user needs to address issues (e.g. unsaved data on a form, etc.)
  4. if valid, take selection and set the listBase to that selectedIndex, otherwise present warnings to user and cancel out the selection process altogether

This does not work as you'd expect. Utilizing the IndexChangeEvent.CHANGING type and the preventDefault works to kill the selection, but at step 4, when I am programmatically setting the selectedIndex of the listBase, it then tries to redispatch the CHANGING event (this despite what the API docs claim).

Here is a sample application src code if you'd like to try this for yourself. I look forward to your comments & solutions.

Thanks. J

http://snipt.org/vUji3#expand

<?xml version="1.0" encoding="utf-8"?>
<s:Application minWidth="955" minHeight="600"
           xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:mx="library://ns.adobe.com/flex/mx"
           xmlns:s="library://ns.adobe.com/flex/spark">
<fx:Script>
    <![CDATA[
        import flash.utils.setTimeout;

        import mx.core.mx_internal;

        import spark.events.IndexChangeEvent;

        use namespace mx_internal;

        [Bindable]
        private var doPreventDefault:Boolean;

        [Bindable]
        private var delayMS:uint = 500;

        private function buttonbar_changingHandler( event:IndexChangeEvent ):void
        {
            // TODO Auto-generated method stub
            if ( doPreventDefault )
            {
                event.preventDefault();

                setTimeout( delayedLogic, delayMS, event.newIndex );
            }
        }

        private function delayedLogic( index:int ):void
        {
            //disabling this results in an endless loop of trying to set the selected index
            //              doPreventDefault = false;

            //this should NOT be hitting the changing handler since we're supposed to be dispatching a value commit event instead.
            bb.setSelectedIndex( index, false );
        }
    ]]>
</fx:Script>

<fx:Declarations>

    <!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>

<s:layout>
    <s:VerticalLayout horizontalAlign="center"/>
</s:layout>

<s:ButtonBar id="bb"
             changing="buttonbar_changingHandler(event)">
    <s:dataProvider>
        <s:ArrayList>
            <fx:String>btn 0</fx:String>
            <fx:String>btn 1</fx:String>
            <fx:String>btn 2</fx:String>
        </s:ArrayList>
    </s:dataProvider>
</s:ButtonBar>

<s:CheckBox label="preventDefault?"
            selected="@{ doPreventDefault }"/>

<s:NumericStepper maximum="5000" minimum="500"
                  stepSize="500" value="@{ delayMS }"/>
</s:Application>
Was it helpful?

Solution

Looking at the SDK, the IndexChangeEvent.CHANGING event is actually preventable - despite the documentation here says that cancelable is false, so my bad on that (although ASDoc went a little sideways), however things get a little interesting from here.

In ListBase @1296 this is only ever dispatched from the commitSelection(dispatchEvents:Boolean = true) method. In ButtonBarBase:dataProvider_changeHandler() is the only place that specifically calls to not dispatch the event, but in ListBase, it's called in commitProperties@939 when there is a proposedSelectionIndex.

So from your code above, if you are trying to set the selection - this is going to call the commitSelection, which I believe is causing the call stack issue. The Timer delay is just going to exacerbate the issue, since at 500ms the UI will have gone through its invalidation cycle at least once, meaning the commitSelection will be executed again because of an invalidateProperties flag is being set from the proprosedSelectionIndex eventually stemming from setSelectedIndex @729

So how to fix this.
I would look at only doing the prevent if the validation fails, otherwise allow it to proceed as normal. If it does fail, call the prevent, set an errorString or equivalent, but don't attempt to change the selection.

[edit] Read RiaStar's comment, and I just concurred with the same 'solution'.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top