Flex 4.6 Mobile - ItemRenderers and application data - actionscript-3

A question about ItemRenderers: let's say I have an ArrayCollection that is my application data sitting inside a global object. I them populate a sparks list with this data, setting the ArrayCollection as the dataProvider.
So each ItemRenderer gets a copy of an item sitting in the array. You can override the "set data" method to set the data something more domain-specific. The problem is that the data is a copy of the original item.
Now let's say we want to add some data to the item while inside the ItemRender. For example, it could call a method on the item telling it to load some details about itself, or maybe we allow the user to modify something on the item.
Obviously, we can't do any of this if we are operating on a copy because it will be thrown away as soon as the ItemRenderer is destroyed and the original object doesn't know anything about what happened.
So what's the best practice? Should I just use the itemIndex of the renderer to pull out the original item from my global array like this:
{globalArrayCollection}.getItemAt(this.itemIndex)
But it seems kind of clunky to me. Is there a best practice for dealing with this?

Not sure I'm following but it sounds like you're looking for a way to get at your item renderer to set/change a value.
You could go about accessing a method on the renderer directly.
In your renderer:
public function setSomeValue(aValue:String):void{
someString = aValue;
}
You would also set the data on your ArrayCollection as well.
To access this method you would use this:
var dataGroup:DataGroup = list.dataGroup;
var itemRenderer:YourItemRenderer = dataGroup.getElementAt(list.selectedIndex) as YourItemRenderer;
itemRenderer.setSomeValue("string");

Hmm, why do you think that original ArrayCollection won't change if you change values in itemRenderer? For me this works and initial ArrayCollection changes.
[Bindable]
protected var model:Model;
override public function set data(value:Object):void
{
super.data = value;
this.model = value as Model;
}
protected function changeValue():void
{
model.value = "newValue";
}
Or am I misunderstood something?

Related

Change data template when the user selects it

I have two datatemplates. One is the default and the other one is for when the user selects this item. I need to give the selected item double width and height of the normal template. How can I do this?
What you want to do is not difficult, but it is not solved by swapping the data template. Instead, it is accomplished by using Visual States in XAML. A Visual State allows you to create multiple "views" of your XAML (for example, what it looks like when it is selected and when it is not selected) and to switch between those easily. Swapping data templates is a big deal, Mostafa, and can result in your UI flickering because the underlying subsystem has to re-render so many parts of the visual tree.
If you want to learn more about the Visual States, you might read over the blog article I wrote on the same subject.
http://blog.jerrynixon.com/2013/11/windows-81-how-to-use-visual-states-in.html
The only problem now is to figure out how to trigger the visual state when the item in a gridview or listview is selected. First, you should know that IsSelected is a property on the gridviewitem or listviewitem control that houses your item. However, it's tricky to reach that property and the most common approach is to sub-class your gridview/listview and override PrepareContainerForItemOverride and set the binding in code-behind.
Like this:
class MyModel
{
public bool IsSelected { get; set; }
}
class MyList : Windows.UI.Xaml.Controls.ListView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
var model = item as MyModel;
var listViewItem = element as Windows.UI.Xaml.Controls.ListViewItem;
var binding = new Windows.UI.Xaml.Data.Binding
{
Source = model,
Mode = Windows.UI.Xaml.Data.BindingMode.TwoWay,
Path = new PropertyPath(nameof(model.IsSelected)),
};
listViewItem.SetBinding(Windows.UI.Xaml.Controls.ListViewItem.IsSelectedProperty, binding);
base.PrepareContainerForItemOverride(element, item);
}
}
I hope this helps.
Best of luck!

Flex Dropdownlist dataprovider reset on refresh

I have couple of dropdownlist controls, that shares the same dataprovider(same reference).
I had overridden the set dataprovider method for a sort function.(code below). The issue is that, when I set this shared dataprovider to a new dropdownlist, all the existing dropdown contorls sharing the dataprvider gets unselected(loses its previously selected values).
override public function set dataProvider(value:IList):void{
if(value is ArrayCollection){
var sort:Sort=new Sort();
var sortField:SortField = new SortField();
sortField.numeric=false;
sort.fields=[sortField];
ArrayCollection(value).sort=sort;
ArrayCollection(value).refresh();
}
super.dataProvider=value;
}
There are a ton of isues sharing the dataProvider between components. We've run into this with a lot of clients using our AutoCompleteComboBox.
You can easily use the same source, but a different--separate--collection for each of your dataProviders.
var dataProvider1 :ArrayCollection = new ArrayCollection(someArray);
var dataProvider2 :ArrayCollection = new ArrayCollection(someArray);
var dataProvider3 :ArrayCollection = new ArrayCollection(someArray);
Each collection is just a wrapper around the base source. Sorting one will not affect any of the others, leaving your other ComboBoxes or DropDownLists untouched.
I did no research on this, but there are two issues/ideas coming up:
if you literally use the same reference to the same arraycollection, you do not need to sort this array more than once (and you actually do this by assigning the same arraycollection more than once)
if it about only single-selection dropdowns, then there is a simple solution:
var oldSelected : TypeOfItem = selectedItem as TypeOfItem;
// do the sort (like in your code)
super.dataProvider=value;
selectedIndex = getItemIndex(oldSelected);

Make Flex TextInput show no prompt on empty String

I am using a s:TextInput in Flex 4.5. It shows it's prompt text if the underlying text value is null or empty String. Does anybody know if I can make either don't show the prompt on empty String or even show a different prompt?
I already found a way by extending the TextInput class and overriding some of the methods but I am still hoping anyone here knows an easier way ;-)
Ok, so based on the comments, here it is:
You store the current prompt value in a private variable, like so:
private var _inputPrompt:String = "";
Then you create a getter, so the value is accessible from outside of this class:
public function get inputPrompt():String
{
return _inputPrompt;
}
Now you can bind inputPrompt anywhere you need it, however, the problem is the getter won't be recalled once the private value changes. You can fix this very easily: Create an update method, for example like so:
public function updateInputPrompt(value:String):void
{
_inputPrompt = value;
}
Ok, nothing fancy so far. I'm guessing this is the point where you are right now. In order to "force" the getter to be recalled, you have to bind it to an event, like so:
[Bindable(event="inputPromptUpdated")]
public function get inputPrompt():String
{
return _inputPrompt;
}
Finally, you can simply dispatch this event when the value is update (i.e. in the updateInputPrompt method):
public function updateInputPrompt(value:String):void
{
_inputPrompt = value;
dispatchEvent("inputPromptUpdated"); // For binding
}
This way, the getter will be recalled every time you dispatch that event.
Hope this helps. Have a great day, and a great weekend!

AS3: How to know if dataprovider or its content(s) is changed

I'm implementing some kind of combobox control (by extending spark.components.supportClasses.DropDownListBase)
Now, inside this control; I need to know:
if the dataprovider is changed/assigned. (which I can do... the first approach below works);
if any item in the dataprovider collection has changed.
I tried 2 methods that did not do the trick...
1ST APPROACH:
[Bindable("collectionChange")]
override public function set dataProvider(value:IList):void
{
if (value) value.addEventListener(CollectionEvent.COLLECTION_CHANGE, onDataChange);
super.dataProvider = value;
trace("DATA CHANGED"); //fires
}
protected function onDataChange(event:CollectionEvent):void
{
trace("COLLECTION ITEM(S) CHANGED"); //does not fire
}
2ND APPROACH:
Since this is based on DropDownListBase; it should dispatch the CollectionEvent.COLLECTION_CHANGE event already..?
public function myClass() //constructor
{
addEventListener(CollectionEvent.COLLECTION_CHANGE, onDataChange);
}
protected function onDataChange(event:CollectionEvent):void
{
trace("DATA CHANGED"); //does not fire
}
Any ideas?
UPDATE: Edited above.. The first approach lets me know if the dataprovider is changed but not if any item is updated in the dataprovider collection. The second approach does not work at all..
We'll be able to help significantly more if you show us a runnable sample to demonstrate the problem.
if the dataprovider is
changed/assigned.
Your first approach should work. Can you tell us what makes you think it didn't? ( No trace statement I assume? ). And tell us what you did to change the dataProvider.
The second approach won't work because myClass is not firing off the collectionChange event.
2 if any item in the dataprovider collection has changed.
There is not really a way to tell this. In most cases, a collection is just a list of pointers to other objects. If you change those pointers, then a collectionChange event is fired. IF you change the item that he pointer is pointed to, the collection has no way to know that something changed. Binding works very similarly if your an MXML fan.
If you have control over how items are changed, you can deal with it that way. Instead of:
(collection.getITemAt(x) as myObject).property = newValue;
Do something like this:
var myObject : MyObject = collection.getITemAt(x) as myObject
myObject.property = newValue;
collection.setItemAt(x, myObject);
I would expect that to fire a collectionChange event, but not the former.
That said, in the context of a dropDownListBase: As you scroll or open and close the drop down, the itemRenderers should be updated to reflect the most current dataProvider's data. But if you change something on the fly while the drop down is open, I would not expect it to update automatically [if you're not changing the dataProvider.

Actionscript - Variable Assignment without reference?

Should be easy. I have an object. I want to modify it, but before i do I want to save a copy of it that I can go back to. I tried setting copy = original but when i modify the attributes of the original the copy also shows the changes. I am assuming this is because in actionscript any time you assign, it really just stores a reference to the original object. So whats the best way for me to store a copy of the original object for later use?
var newObj:Object = Object(ObjectUtil.copy(oldObj));
"Copies the specified Object and returns a reference to the copy. The copy is made using a native serialization technique. This means that custom serialization will be respected during the copy.
This method is designed for copying data objects, such as elements of a collection. It is not intended for copying a UIComponent object, such as a TextInput control. If you want to create copies of specific UIComponent objects, you can create a subclass of the component and implement a clone() method, or other method to perform the copy."
http://livedocs.adobe.com/flex/3/langref/mx/utils/ObjectUtil.html#copy()
What you are looking for is a deep copy of the object rather then passing by reference. I found the answer here which uses the new ByteArray class in AS3:
http://www.kirupa.com/forum/showthread.php?p=1897368
function clone(source:Object):* {
var copier:ByteArray = new ByteArray();
copier.writeObject(source);
copier.position = 0;
return(copier.readObject());
}
Which you then use like this:
newObjectCopy = clone(originalObject);
Cheers!
// duplicate any given Object (not MCs)
Object.prototype.copy = function()
{
ASSetPropFlags(Object.prototype,["copy"],1);
var _t = new this.__proto__.constructor(this) //
for(var i in this){
_t[i] = this[i].copy()
}
return _t
};
Usage
x = ["1","2","3",[4,5],[{a:1,b:2}]]
y = x.copy()
y[0] = 0
y[3][0]="d"
trace(x)
trace(y)