Flex: Expand AdvancedDataGrid Tree Column programmatically - apache-flex

Does anyone know how to programmatically expand the nodes of an AdvancedDataGrid tree column in Flex? If I was using a tree I would use something like this:
dataGrid.expandItem(treeNodeObject, true);
But I don't seem to have access to this property in the AdvancedDataGrid.

AdvancedDataGrid has an expandItem() method too:
http://livedocs.adobe.com/flex/3/langref/mx/controls/AdvancedDataGrid.html#expandItem()

Copy the sample found at the aforementioned url and call this function:
private function openMe():void
{
var obj:Object = gc.getRoot();
var temp:Object = ListCollectionView(obj).getItemAt(0);
myADG.expandItem(temp,true);
}

You could also open nodes by iterating through the dataProvider using a cursor. Here is how I open all nodes at a specified level:
private var dataCursor:IHierarchicalCollectionViewCursor;
override public function set dataProvider(value:Object):void
{
super.dataProvider = value;
/* The dataProvider property has not been updated at this point, so call
commitProperties() so that the HierarchicalData value is available. */
super.commitProperties();
if (dataProvider is HierarchicalCollectionView)
dataCursor = dataProvider.createCursor();
}
public function setOpenNodes(numLevels:int = 1):void
{
dataCursor.seek(CursorBookmark.FIRST);
while (!dataCursor.afterLast)
{
if (dataCursor.currentDepth < numLevels)
dataProvider.openNode(dataCursor.current);
else
dataProvider.closeNode(dataCursor.current);
dataCursor.moveNext();
}
dataCursor.seek(CursorBookmark.FIRST, verticalScrollPosition);
// Refresh the data provider to properly display the newly opened nodes
dataProvider.refresh();
}

Would like to add here that the AdvancedDataGrid, in spite of having an expandAll() method, has a property called displayItemsExpanded, which set to true will expand all the nodes.
For expanding particular children, the expandChildrenOf() and expandItem() methods can be used, as can be verified from the links given above.

Related

How to keep a list from scrolling on dataProvider refresh/update/change?

I have a simple list and a background refresh protocol.
When the list is scrolled down, the refresh scrolls it back to the top. I want to stop this.
I have tried catching the COLLECTION_CHANGE event and
validateNow(); // try to get the component to reset to the new data
list.ensureIndexIsVisible(previousIndex); // actually, I search for the previous data id in the IList, but that's not important
This fails because the list resets itself after the change (in DataGroup.commitProperties).
I hate to use a Timer, ENTER_FRAME, or callLater(), but I cannot seem to figure out a way.
The only other alternatives I can see is sub-classing the List so it can catch the dataProviderChanged event the DataGroup in the skin is throwing.
Any ideas?
Actually MUCH better solution to this is to extend DataGroup. You need to override this.
All the solutions here create a flicker as the scrollbar gets resetted to 0 and the it's set back to the previous value. That looks wrong. This solution works without any flicker and the best of all, you just change DataGroup to FixedDataGroup in your code and it works, no other changes in code are needed ;).
Enjoy guys.
public class FixedDataGroup extends spark.components.DataGroup
{
private var _dataProviderChanged:Boolean;
private var _lastScrollPosition:Number = 0;
public function FixedDataGroup()
{
super();
}
override public function set dataProvider(value:IList):void
{
if ( this.dataProvider != null && value != this.dataProvider )
{
dataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE, onDataProviderChanged);
}
super.dataProvider = value;
if ( value != null )
{
value.addEventListener(CollectionEvent.COLLECTION_CHANGE, onDataProviderChanged);
}
}
override protected function commitProperties():void
{
var lastScrollPosition:Number = _lastScrollPosition;
super.commitProperties();
if ( _dataProviderChanged )
{
verticalScrollPosition = lastScrollPosition;
}
}
private function onDataProviderChanged(e:CollectionEvent):void
{
_dataProviderChanged = true;
invalidateProperties();
}
override public function set verticalScrollPosition(value:Number):void
{
super.verticalScrollPosition = value;
_lastScrollPosition = value;
}
}
I ll try to explain my approach...If you are still unsure let me know and I ll give you the source code as well.
1) Create a variable to store the current scroll position of the viewport.
2) Add Event listener for Event.CHANGE and MouseEvent.MOUSE_WHEEL on the scroller and update the variable created in step 1 with the current scroll position;
3) Add a event listener on your viewport for FlexEvent.UpdateComplete and set the scroll position to the variable stored.
In a nutshell, what we are doing is to have the scroll position stored in variable every time user interacts with it and when our viewport is updated (due to dataprovider change) we just set the scroll position we have stored previously in the variable.
I have faced this problem before and solved it by using a data proxy pattern with a matcher. Write a matcher for your collection that supports your list by updating only changed objects and by updating only attributes for existing objects. The goal is to avoid creation of new objects when your data source refreshes.
When you have new data for the list (after a refresh), loop through your list of new data objects, copying attributes from these objects into the objects in the collection supporting your list. Typically you will match the objects based on id. Any objects in the new list that did not exist in the old one get added. Your scroll position will normally not change and any selections are usually kept.
Here is an example.
for each(newObject:Object in newArrayValues){
var found:Boolean = false;
for each(oldObject:Object in oldArrayValues){
if(oldObject.id == newObject.id){
found = true;
oldObject.myAttribute = newObject.myAttribute;
oldObject.myAttribute2 = newObject.myAttribute2;
}
}
if(!found){
oldArrayValues.addItem(newObject);
}
}
My solution for this problem was targeting a specific situation, but it has the advantage of being very simple so perhaps you can draw something that fits your needs from it. Since I don't know exactly what issue you're trying to solve I'll give you a description of mine:
I had a List that was progressively loading data from the server. When the user scrolled down and the next batch of items would be added to the dataprovider, the scrollposition would jump back to the start.
The solution for this was as simple as stopping the propagation of the COLLECTION_CHANGE event so that the List wouldn't catch it.
myDataProvider.addEventListener(
CollectionEvent.COLLECTION_CHANGE, preventRefresh
);
private function preventRefresh(event:CollectionEvent):void {
event.stopImmediatePropagation();
}
You have to know that this effectively prevents a redraw of the List component, hence any added items would not be shown. This was not an issue for me since the items would be added at the end of the List (outside the viewport) and when the user would scroll, the List would automatically be redrawn and the new items would be displayed. Perhaps in your situation you can force the redraw if need be.
When all items had been loaded I could then remove the event listener and return to the normal behavior of the List component.

Extending DropDownList to include an extra option

I want to extend DropDownList control to include an option for creating or editing the options. For example; for a list of projects in the dropdown list, there will be another option that says "Create new project..." or "Edit projects..." and this will be the last option in the list. When user selects this option, the selectedIndex or selectedItem will not change and corresponding action will be taken (for example a popup window shows up). This will be a convenient way for the end user.
Now I want this to work independent of the context and the class must be reusable. User will only specify the optionText and optionFunction to work this out. The basic structure of the class looks like this:
public class OptiveDropDownList extends DropDownList
{
private var _enableOption:Boolean;
private var _optionText:String;
private var _originalDataProvider:IList;
[Bindable] public var optionFunction:Function;
public function OptiveDropDownList()
{
super();
}
public function set optionText(value:String):void
{
_optionText = value;
dataProvider = _originalDataProvider;
}
public function set enableOption(value:Boolean):void
{
_enableOption = value;
dataProvider = _originalDataProvider;
}
public override function set dataProvider(value:IList):void
{
_originalDataProvider = value;
var dp:IList = null;
if(!value){
dp=new ArrayCollection(value.toArray());
if(_enableOption){
var opt:Object=new Object();
opt[labelField]=_optionText;
dp.addItem(opt);
}
}
super.dataProvider = dp;
}
[Bindable]
public override function get dataProvider():IList
{
return _originalDataProvider;
}
}
I hope my code is clear to understand, I am adding an extra object to the dataprovider for the option. Field names are self-explanatory.
Now my question is how to know whether the dataprovider's items have changed? Which functions should I override and how to do it. I have tried using a ChangeWatcher to watch the length property of the dataprovider, but it doesnt work if only an object in the dataprovider has changed. I need to capture these changes and update the view.
I also need to capture the selection and call optionFunction, preventing the default action not to give index out of bounds error.
Thanks in advance.
Just add an event listener to the original dataProvider. All implementations of IList should dispatch CollectionEvent.COLLECTION_CHANGE when the the list changes (e.g. add, remove or when an existing object in the list has been changed). In your event handler you can update the DropDownList's dataProvider accordingly.
By overriding the mx_internal method setSelectedIndex() you can adjust the selection according to your wishes. Take a look at the blog post "Disable selection on some items in a spark List" for some inspiration.

Auto select the first index in a sparks List control in flex4

I have a spark List control. It has a dataProvider that shows reply from twitter search.
I have created a function for change handler like this:
protected function list_changeHandler(event:IndexChangeEvent):void
{
ta.text = coverflow.selectedItem.title;
}
So, whenever I select each of the items in the List, I will see the message(ta.text)
but now, instead of me manually clicking the first time, I want it to automatically click/select the first item and see the first message(ta.text)
How can I achieve this?
You should set the requireSelection property to true on the list control
<s:List id="myList" dataProvider="{myDataProvider}" requireSelection="true"/>
How about to try this solution? :)
Your list control also has event name creationComplete (similar to change event). Try to select your first item with this:
protected function list1_creationCompleteHandler(event:FlexEvent):void
{
if(event.target.dataProvider != null )
{
(event.target as List).selectedIndex = 0;
}
}
You may not need to convert event.target to List. but it may help you access code completion while you are coding.
Actually, in thinking about this, you probably need to subclass the list and override the dataProvider setter.
override public function set dataProvider(data:*) : void {
super._dataProvider = data;
// This will be an ArrayCollection or XMLListCollection, so will have a length
if (data && data.length > 0) {
this.selectedIndex = 0;
invalidateDisplayList();
}
}
Set the dataProvider of the list, set the selected item, and then either call your handler function directly with a null parameter, or make the list dispatch an indexChanged event so that your handler function gets executed.

How do I make sure the text of an ActionScript TextInput is updated when the Object property defining that text is updated?

Not an easy question to decipher, so let me boil it down. I'm trying to convert an MXML component to an ActionScript Class. The component consists of a Form with a TextInput, TextArea, and two buttons - Save and Cancel, and a Validator for the TextInput, and other logic to handle events that occur. This component is currently extended by several other components.
Now, in the MXML component binding the TextInput text property to a property in an Object was very easy:
<mx:TextInput text="{_itemToEdit.name}" />
But in ActionScript, I'm creating the TextInput and setting the text property before the Object is set, and the TextInput is not being updated:
public var itemToEdit:Object = {};
private var nameInput:TextInput = new TextInput();
public function MyClass()
{
nameInput.text = itemToEdit.name;
}
How can I make sure that the TextInput text property is bound to the specified property in the Object?
Binding is all about firing change events. you'll need to modify your 'itemToEdit' class to be an EventDispatcher for this hack to work. here goes
//item to edit class
private var _name:String;
public function set name(value:String):void
{
_name = value;
dispatchEvent(new Event("NAME_CHANGED"));
}
//whatever the binding class is
private var _itemToEdit:ItemToEdit;
private var _textField:TextField;
public function set itemToEdit(value:ItemToEdit):void
{
if (_itemToEdit) removeEventListeners();
_itemToEdit = value;
if (_itemToEdit) addEventListeners();
}
private function addEventListeners():void
{
_itemToEdit.addEventListener("NAME_CHANGED", itemToEdit_nameChangedHandler);
itemToEditChangedHandler(null);
}
private function itemToEdit_nameChangedHandler(event:Event):void
{
_textField.text = _itemToEdit.name;
}
Obviously this was done just for speed, you'll need custom events and some better names etc, but this is the basic jist.
Apparently it's slightly more complex than a simple assignment to bind purely in AS, here's a couple tutorial/docs to show you how to pull it off.
http://cookbooks.adobe.com/index.cfm?event=showdetails&postId=6802
http://raghuonflex.wordpress.com/2007/08/30/binding-in-mxml-as/
Compile your MXML component with the -keep option. Examine the ActionScript code that was generated by mxmlc and do something similar.
You may also do it using the Proxy object - I blogged about it over here: http://flexblog.faratasystems.com/?p=433
If "itemToEdit" is a pure AS3 Object, then the binding probably doesn't work properly anyway. That is, it will work when the object is initially created, but any changes to "name" in the object won't be detected. (I could be wrong...haven't done extensive tests)
Anyway, your problem is easy to solve with getters/setters:
private var _itemToEdit:Object;
public function get itemToEdit():Object { return _itemToEdit; }
public function set itemToEdit(value:Objecy):void {
_itemToEdit = value;
nameInput.text = value.name;
}
Binding isn't necessary here.

Triggering a change event in a Flex tree control programmatically

I have a method to add an XML node structure to the currently selected tree node.
This appends the xml, and opens the parent node to display the newly added node.
I then select the node by setting the selectedItem of the tree.
I have an editing form that updates its values on the tree change event. When I set the selectedItem in this method, The node is selected correctly but the change event never fires (thus the editor doesnt update). I have tried to call it in a call later block to no avail.
Is there a way I can force the tree to dispatch a change event at this point?
public function addSelected(node:XML):void{
tree_expandItem(false);
var selectedItem:XML = tree.selectedItem as XML;
selectedItem.appendChild(node);
tree_expandItem(true);
callLater(function():void { tree.selectedItem = node; } );
}
To extend this question in a general sort of way - I would have thought that changing the selectedItem of the tree would result in a change event anyway? Or is a change only considered a change if the user makes it?
You could move the logic that is currently in your change event handler to a separate function, and then call that function directly:
private function changeHandler(event:ListEvent):void
{
doChangeLogic();
}
private function doChangeLogic():void
{
//statements
}
public function addSelected(node:XML):void
{
tree_expandItem(false);
var selectedItem:XML = tree.selectedItem as XML;
selectedItem.appendChild(node);
tree_expandItem(true);
callLater(function():void { tree.selectedItem = node; } );
doChangeLogic();
}
Is there a way I can force the tree to dispatch a change event at this point?
Use the dispatchEvent() method. Thanks James!

Resources