views:

2276

answers:

4

In my Flex interface, I've got a few List components that are data bound to ArrayCollections. I'm finding that when the ArrayCollections are updated (by Web services) and the data bindings for the Lists update, each List is reset and its selectedIndex is dropped. This is a problem if a user is in the middle of a task and has already selected an item in the List, but hasn't yet submitted the form.

I know how I can kludge together some ActionScript to deal with these cases, but do you know of any way to handle this more naturally using Flex's data binding or general-purpose event utilities?

As always, thanks!

+1  A: 

I like using the observe class found here to call a function when data changes. It takes two parameters: data to watch, and a function to call. (Saves you time of repeating setters and getters all the time)

I would stored the selectedItem when it is selected and when the data is updated reselect this item by looping through the list and selecting the item again.

Brandon
+1  A: 

We have a SelectionRestorer class that does just this. Just add it to the MXML and set it's target to the list/combobox/whatever you want to repopulate, and it does all the watching for you.

HTH,

Sam


We're hiring! Developers and QA in Washington, DC area (or looking to relocate) should send resumes to [email protected].


package com.atellis.common.controls {
import flash.events.Event;
import flash.events.EventDispatcher;

import mx.events.CollectionEvent;
import mx.events.ListEvent;

public class SelectionRestorer extends EventDispatcher
{
 private var previousSelectedItems:Array;

 private var _compareField:String;
 [Bindable("compareFieldChanged")]
 public function get compareField():String {
  return _compareField;
 }
 public function set compareField(value:String):void {
  _compareField = value;
  dispatchEvent(new Event("compareFieldChanged"));
 }

 private var _compareFunction:Function;
 [Bindable("compareFunctionChanged")]
 public function get compareFunction():Function {
  return _compareFunction;
 }
 public function set compareFunction(value:Function):void {
  _compareFunction = value;
  dispatchEvent(new Event("compareFunctionChanged"));
 }

 private var _target:Object;
 [Bindable("targetChanged")]
 public function get target():Object {
  return _target;
 }

 public function set target(value:Object):void {
  if (_target) {
   if (_target.dataProvider) {
    _target.dataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE, dataChangeHandler);
   }
   _target.removeEventListener(ListEvent.CHANGE, selectionChangeHandler);
   _target.removeEventListener(CollectionEvent.COLLECTION_CHANGE, dataChangeHandler);    
  }
  _target = value;
  if (value) {
   value.addEventListener(ListEvent.CHANGE, selectionChangeHandler, false, 0, true);
   value.addEventListener(CollectionEvent.COLLECTION_CHANGE, dataChangeHandler, false, 100.0, true);
   if (value.dataProvider) {
    value.dataProvider.addEventListener(CollectionEvent.COLLECTION_CHANGE, dataChangeHandler, false, 100.0, true);
   }
  }
  dispatchEvent(new Event("targetChanged"));
 }

 public function refresh():void {
  if (useMultipleSelect) {
   previousSelectedItems = target.selectedItems;
  } else {
   previousSelectedItems = [target.selectedItem];
  }
 }

 public function clear():void {
  previousSelectedItems = null;
 }

 private function compareFieldFunction(o1:Object, o2:Object):Boolean {
  if (o1 == null || o2 == null) {
   return false;
  }
  var v1:Object = o1[compareField];
  var v2:Object = o2[compareField];
  return v1 != null && v2 != null && v1 == v2;    
 }

 private function compareEqualityFunction(o1:Object, o2:Object):Boolean {
  if (o1 == null || o2 == null) {
   return false;
  }
  return o1 == o2;    
 }

 private function get useMultipleSelect():Boolean {
  return "selectedItems" in target && "allowMultipleSelection" in target && target.allowMultipleSelection;
 }

 private function selectionChangeHandler(event:ListEvent):void {
  refresh();
 }

 private function dataChangeHandler(event:CollectionEvent):void {

  if (previousSelectedItems == null || previousSelectedItems.length == 0) {
   return;
  }

  if (target.dataProvider == null) {
   previousSelectedItems = null;
   return;
  }

  var comparer:Function = compareFunction;

  if (comparer == null) {
   if (compareField == null) {
    comparer = compareEqualityFunction;
   } else {
    comparer = compareFieldFunction;
   }
  }

  // when you assign an array to ListBase.selectedItems, it removes all items from the array,
  // so we need to build two arrays, one to assign, and one for current state
  var indices:Array = new Array();
  var items:Array = new Array();
  var dpIndex:int = 0;   
  for each(var newItem:Object in target.dataProvider) {
   for(var i:int = 0; i<previousSelectedItems.length; i++) {
    if (comparer(newItem, previousSelectedItems[i])) {
     indices.push(dpIndex);
     items.push(newItem);
     previousSelectedItems.splice(i, 1);
     break;
    }      
   }
   if (previousSelectedItems.length == 0) {
    break;
   }
   dpIndex++;
  }
  target.validateNow();

  if (useMultipleSelect) {
   target.selectedIndices = indices;
  } else {
   target.selectedIndex = indices[0];
  }

  if (items.length) {
   previousSelectedItems = items;
  } else {
   previousSelectedItems = null;
  }
 }    
}
}
A: 

thanks for the code, but it doesnt work here: TypeError: Error #1006: value is not a function. on line 48 while value is a ComboBox useing Flex3

A: 

Phil,

I ran into this error as well and it was because I didn't put curly braces around the target value.

Jesse