Triggering a change event in a Flex tree control programmatically - apache-flex

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!

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.

Help with containers

I am using view stack...so when view change like when we move from one page to another hide event is dispatched.So i am saving the information of last page in hide event before i go to next page.but thing is that if i change nothing still change on view hide event is invoked nd call go to backend...i just want do call only if sumthing change in the view..like sum text value...So i have two options
use event listener on each component if sumthing change its make the flag true...nd hide event check, if flag is true send call to backend.
event listener at container level ..if sumthing change in child componenet through bubbling container knows if sum event is dispatched.nd makes the flag true.
I have doubt with container...
Can i use container, and how?
Reason why I can't use container?
What are the pros and cons either way?
I would recommend using a dataProvider with the ability to compare them. For instance, if you are changing things with textinputs, you could basically do something like this:
[Bindable]
private var myDataProvider:Object = new Object();
private function creationCompleteHandler():void {
myDataProvider.updated = false;
myDataProvider.defaultValue = 'default';
myDataProvider.defaultValueTwo = 'default';
}
etc.
Then, in your mxml, you can have something like this:
<mx:TextInput id="myText" text="{myDataProvider.defaultValue}" change="myDataProvider.defaultValue=myText.text; myDataProvider.updated=true;" />
Lastly, in your hide event, you can do the following:
private function hideEventHandler( event:Event ):void {
if( myDataProvider.updated ){
// Call your RemoteServices (or w/e) to update the information
}
}
This way, when anything changes, you can update your dataProvider and have access to the new information each time.
Hope this helps!
I've used an approach similar to your first option in a couple of my past projects. In the change event for each of my form's controls I make a call to a small function that just sets a changesMade flag to true in my model. When the user tries to navigate away from my form, I check the changesMade flag to see if I need to save the info.
Data models are your friend!
If you get in the habit of creating strongly typed data models out of your loaded data, questions like this become very basic.
I always have a key binding set to generate a code snipit similar to this...
private var _foo:String;
public function get foo():String
{
return _foo;
}
public function set foo(value:String):void
{
if(_foo == value)
return;
var oldVal:String = _foo;
_foo = value;
this.invalidateProperty("foo", oldVal, value);
}
If your data used getters/setters like this, it would be very easy to validate a change on the model level, cutting the view out of the process entirely.

Flexlib scheduleViewer.. how to handle clicks on items

I'm trying to use a flexlib schedule viewer in my application.
I want to have it so that when I click on a scheduled event, it calls a function in my main app (that will allow me to edit the event). But there doesn't seem to be any specific function for anything like this built into the class ie no event dispatched when I click on an event.
I can use the 'click' function to detect that the item has been clicked on.. and have tried something like this:
private function exerciseClickHandler(event:MouseEvent):void{
if (exerciseSeries.selectedItem != null){
//code
}
}
<code:ScheduleViewer id="exerciseSeries" click="exerciseClickHandler(event)" />
This method isn't very reliable because if it only works the first time.. once an item is selected, it stays selected so all following clicks on the item fulfills the condition.
Is there any way to determine whether an event was being clicked on?
Or do I have to extend the component and add some sort of clickEvent when an event is clicked on.
Since exerciseClickHandler is firing up when you click on the component, wouldn't this work?
Instead of
private function exerciseClickHandler(event:MouseEvent):void{
if (exerciseSeries.selectedItem != null){
//code
}
}
write
private function exerciseClickHandler(event:MouseEvent):void{
switch (exerciseSeries.selectedItem)
{
//code
case xy:
break;
}
}
or
private function exerciseClickHandler(event:MouseEvent):void{
//do something with exerciseSeries.selectedItem
}
What I mean is that you wrote that everything stops after the first element is clicked. And according to the code you provided it has to stop, beacuse after the first click exerciseSeries.selectedItem won't be null anymore, since it's selected. So remove the conditional you wrote and use the instance.
I'd suggest you set up a ChangeWatcher to keep an eye on the selectedItem (or selectedItems if you are going to allow multiple selection at some point). Example:
protected exerciseSeriesCreationCompleteHandler(event:FlexEvent):void{
ChangeWatcher.watch(this,['exerciseSeries','selectedItem'], handleChange_SelectedItem);
}
protected function handleChange_SelectedItem(event:PropertyChangeEvent):void{
// Either
dispatchedEvent(//some custom event);
// Or
someDirectMethodCall();
}
An alternative would be to search for an instance of the the event class in the view hierarchy under the mouse coordinates whenever a user clicks.
//Attach this click handler to the component
private function handleClick(event : MouseEvent) : void {
var obj : *EventClass*= null;
var applicationStage : Stage = FlexGlobals.topLevelApplication.stage as Stage;
var mousePoint : Point = new Point(applicationStage.mouseX, applicationStage.mouseY);
var objects : Array = applicationStage.getObjectsUnderPoint(mousePoint);
for (var i : int = objects.length - 1; i >= 0; i--) {
if (objects[i] is *EventClass*) {
obj = objects[i] as *EventClass*;
break;
}
}
if(obj is *EventClass*){
//Dispatch some custom event with obj being the item that was clicked on.
}
}
Where EventClass is the class of the objects that represent events
I have had similar problems and sometimes you can get by with wrapping the object with a Box and putting the click event on the Box. If you have not already tried that, it's a cheap, easy fix (if it works for you).
<mx:Box click="exerciseClickHandler(event)">
<code:ScheduleViewer id="exerciseSeries" />
</mx:Box>

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.

Flex: Expand AdvancedDataGrid Tree Column programmatically

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.

Resources