Properly using a flex List with sorting and filtering is tricky.

Take what we've learned and make a generic class for watching
a DSet and making a nice List out of it that preserves your
scroll position while elements are added or removed.


git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@790 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Ray Greenwell
2009-03-24 21:03:14 +00:00
parent afc8a7e4c7
commit 02748d3034
+200
View File
@@ -0,0 +1,200 @@
//
// $Id$
package com.threerings.flex {
import mx.core.IFactory;
import mx.core.ScrollPolicy;
import mx.collections.ArrayCollection;
import mx.collections.Sort;
import mx.controls.List;
import com.threerings.util.Util;
import com.threerings.presents.dobj.AttributeChangedEvent;
import com.threerings.presents.dobj.AttributeChangeAdapter;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DSet;
import com.threerings.presents.dobj.DSet_Entry;
import com.threerings.presents.dobj.EntryAddedEvent;
import com.threerings.presents.dobj.EntryUpdatedEvent;
import com.threerings.presents.dobj.EntryRemovedEvent;
import com.threerings.presents.dobj.SetAdapter;
/**
* A list that renders the contents of a DSet.
*/
public class DSetList extends List
{
public function DSetList (
renderer :IFactory, sortFn :Function = null, filterFn :Function = null)
{
horizontalScrollPolicy = ScrollPolicy.OFF;
verticalScrollPolicy = ScrollPolicy.ON;
selectable = false;
itemRenderer = renderer;
_data = new ArrayCollection();
var sort :Sort = new Sort();
sort.compareFunction = sortFn;
_data.sort = sort;
_data.filterFunction = filterFn;
_data.refresh();
dataProvider = _data;
}
/**
* Set the aata to be displayed by this List, if not being attached to a DObject.
*/
public function setData (array :Array /* of DSet_Entry */) :void
{
_data.source = (array == null) ? [] : array;
refresh();
}
/**
* Refresh the data, which can be called if something external has changed
* the sort order or the rendering.
*/
public function refresh () :void
{
_data.refresh();
}
/**
* Initialize listening on the specified object.
*
* @param object the DObject on which to listen.
* @param field the name of the DSet field that we're displaying.
* @param refreshFields additional fields that cause refresh() to be called should
* AttributeChangedEvents arrive on them.
*/
public function init (object :DObject, field :String, ... refreshFields) :void
{
shutdown();
_object = object;
_field = field;
_refreshFields = refreshFields;
_attrListener = new AttributeChangeAdapter(attrChanged);
_setListener = new SetAdapter(entryAdded, entryUpdated, entryRemoved);
_object.addListener(_attrListener);
_object.addListener(_setListener);
setEntries(_object[_field] as DSet);
}
/**
* Shut down listening.
*/
public function shutdown () :void
{
if (_attrListener != null) {
_object.removeListener(_attrListener);
_object.removeListener(_setListener);
_attrListener = null;
_setListener = null;
_object = null;
_field = null;
_refreshFields = null;
setData(null);
}
}
/**
* Safely set the entries, even if null.
*/
protected function setEntries (entries :DSet) :void
{
setData((entries == null) ? null : entries.toArray());
}
/**
* Handle a change to the entire set.
*/
protected function attrChanged (event :AttributeChangedEvent) :void
{
var name :String = event.getName();
if (name == _field) {
setEntries(event.getValue() as DSet);
} else if (_refreshFields.indexOf(name) != -1) {
refresh();
}
}
/**
* Handle an addition to the set.
*/
protected function entryAdded (event :EntryAddedEvent) :void
{
if (event.getName() == _field) {
_data.list.addItem(event.getEntry());
refresh();
}
}
/**
* Handle an update to the set.
*/
protected function entryUpdated (event :EntryUpdatedEvent) :void
{
if (event.getName() == _field) {
var entry :DSet_Entry = event.getEntry();
var idx :int = findKeyIndex(entry.getKey());
if (idx == -1) {
throw new Error();
}
_data.list.setItemAt(entry, idx);
refresh();
}
}
/**
* Handle a removal from the set.
*/
protected function entryRemoved (event :EntryRemovedEvent) :void
{
if (event.getName() == _field) {
var idx :int = findKeyIndex(event.getKey());
if (idx != -1) {
_data.list.removeItemAt(idx);
refresh();
}
}
}
/**
* Find the index of the specified entry key from the raw list.
*/
protected function findKeyIndex (key :Object) :int
{
for (var ii :int = 0; ii < _data.list.length; ii++) {
if (Util.equals(key, DSet_Entry(_data.list.getItemAt(ii)).getKey())) {
return ii;
}
}
return -1;
}
/** The collection for the List, This is the filtered/sorted view. For the
* the raw list, we access _data.list. */
protected var _data :ArrayCollection;
/** The object on which we're listening. */
protected var _object :DObject;
/** The fieldname of the DSet in the _object. */
protected var _field :String;
/** Fields on which to watch for updates and call refresh(). */
protected var _refreshFields :Array;
/** Our listeners, so that we don't junk-up our public interface. */
protected var _attrListener :AttributeChangeAdapter;
protected var _setListener :SetAdapter;
}
}