Get item by index in a tree control - apache-flex

I'm doing a drag and drop operation on a tree using some help from Adobe's quick Starts:
http://www.adobe.com/devnet/flex/quickstart/working_with_tree/
The code suggested is roughly this:
var dropTarget:Tree = Tree(evt.currentTarget);
var i:int = dropTarget.calculateDropIndex(evt);
myTree.selectedIndex = i;
var node:XML = myTree.selectedItem as XML;
var drugXML:XML = XML(Tree(evt.dragInitiator).selectedItem);
if(node.localName() != drugXML.localName()){
DragManager.showFeedback(DragManager.NONE);
return;
}else{
DragManager.showFeedback(DragManager.COPY);
}
This is all well and good, but I don't like the way it is selecting(highlighting) each item in the tree I'm dropping on, it's less clear where the item is going to drop. I want to implement this without the selection but there doesn't seem to be a way to get the XML of the node using just the index. I would like something like this:
var dropTarget:Tree = Tree(evt.currentTarget);
var i:int = dropTarget.calculateDropIndex(evt);
var node:XML = myTree.itemAt(i) as XML;
//AFAIK itemAt(i) or anything like it does not exist
var drugXML:XML = XML(Tree(evt.dragInitiator).selectedItem);
if(node.localName() != drugXML.localName()){
DragManager.showFeedback(DragManager.NONE);
return;
}else{
DragManager.showFeedback(DragManager.COPY);
}
So does anyone know what function is, or what I can do to extend the tree to have a function, like "itemAt(i)"
Thanks
~Mike
EDIT: I forgot to post that my current workaround is setting the selectedIndex = -1 after I get my node:XML. I'm afraid that if something bogs on the processor the user may see it select then deselect.

Much simpler, though there may be gotchas:
var index:int = ...
var renderer:IListItemRenderer = tree.indexToItemRenderer(index);
var item:Object = renderer.data;
This won't work if the index is offscreen (since there might not be an active itemRenderer); shouldn't be an issue for drag and drop.

If your dataProvider is explicitly or implicitly a collection (see the linked docs for conversion rules), you should be able to use getItemAt to resolve the index.
It appears that an XML provider will be converted implicitly:
var tree:Tree = ...;
var data:XML = ...;
tree.dataProvider = data; // could just as well be from MXML
var provider:ICollectionView = tree.collection; // tree converted it for us
trace(provider.getItemAt(index));
If you have something other than the convertible types (XML, Array, etc.), you might consider wrapping your dataProvider in an XMLListCollection or what have you to gain access to that method.
The above is aiming in the right direction but missing.
Turns out you don't want the dataProvider since Tree overrides that; you want the collection property (protected). So you could override Tree and provide an indexToItem method to go with the thisToThat methods already present in Tree.

Related

Accent insensitive searching in RadComboBox

I'm relatively new to using ASP webforms and Telerik, but I'm looking for a way that allows me to type special characters (é, ù, à, ...) in a RadComboBox.
Lets say I have a name in my ObjectDataSource called "René Somebody". I need to be able to find him by searching for "Rene" and "René", but so far no luck.
In the application they managed to do this on a RadGrid with filters, but this same solution doesn't work for the RadComboBox as far as I know.
The solution they used in the RadGrid: http://www.telerik.com/forums/accent-insensitive-filtering-filtering-on-a-different-column#YS1QT8P1U0-cRPFNfjvDzA
I have no access to the backend components but the demo you linked contains frontend code and it looks like you can hack in there. It looks like this control may be both client-server and client-side only. For client-side only hacks looks kind of complicated and invloves non-public API (_onInputChange) but for client-server case (which is probably your case) the doc on client side of RadComboBox Object mentions requestItems method so hacking it is probably reasonably future safe:
var hackRadComboBoxFilter = function (combobox, filterProcessingFunction) {
var oldRequestItems = combobox.requestItems;
combobox.requestItems = function() {
var args = Array.prototype.slice.call(arguments);
// requestItems has several arguments but the text seems to be the
// first one, so let's modify it and call the original method
var origFilter = args[0];
args[0] = filterProcessingFunction(origFilter);
oldRequestItems.apply(this, args);
}
};
Unfortunately I don't know a built-in way to deal with accents in JS but you can hack something simple here as well:
var accents = 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž';
var mappedAccents = "AAAAAAaaaaaaOOOOOOOooooooEEEEeeeeeCcDIIIIiiiiUUUUuuuuNnSsYyyZz";
var removeAccents = function (origStr) {
var components = [];
var len = origStr.length;
var afterLastAccent = 0;
for (var i = 0; i < len; i++) {
var mapPos = accents.indexOf(origStr[i]);
if (mapPos != -1) {
components.push(origStr.substr(afterLastAccent, i - afterLastAccent) + mappedAccents[mapPos]);
afterLastAccent = i + 1;
}
}
if (afterLastAccent < len)
components.push(origStr.substr(afterLastAccent, len - afterLastAccent));
return components.join('');
};
So now you can combine it in something like this:
// In real app you probably want something like this
// var targetComboBox = $find("<%= RadComboBox1.ClientID %>");
// but for test let's just hack first combobox on the page
var targetComboBox = Telerik.Web.UI.RadComboBox.ComboBoxes[0];
hackRadComboBoxFilter(targetComboBox, removeAccents);
or if you want to modify all the comboboxes on the page, you can change prototype using the same trick:
hackRadComboBoxFilter(Telerik.Web.UI.RadComboBox.prototype, removeAccents)

HierarchicalCollectionView: One time sort?

I have an AdvancedDataGrid that relies on a HierarchicalCollectionView as it's dataProvider. What I'd like to do is sort the data when it is first loaded, but then disable the sort so that anything that is added after the initial load doesn't cause the grid to auto-sort again. I tried something like this:
this._myDataProvider = new HierarchicalCollectionView(
new HierarchicalData(this._model.rootTasks));
var mySort:Sort = new Sort();
mySort.fields = [new SortField("startDate")];
this._tasksDataProvider.sort = taskSorting;
this._tasksDataProvider.refresh();
this._tasksDataProvider.sort = null;
But setting the sort to null just leaves the data unsorted. I guess what I'm asking is: how can I sort the underlying hierarchical data since it seems setting the sort property will keep it dynamically sorting. Thanks for any help you can provide.
Personally, I would change the sort order when you're getting the data. Either it's done on the server side or when you parse the data (ie. in your model). You can do a one time sort using Array with sortOn.
you can
1. sort the original data with sort function,
2. clone content and put it to a new collection with no sort (be careful do and make a manual clone),
3. just use the new data collection.
I had the same kind of problem until I realized that the sorting with Sort object does not change the "physical" ordering of the items within the Collection, so when you remove the Sort, the next refresh reverts the view to the actual "physical" ordering.
Similarily as stated above, I solved by it by cloning the sub-collections into sorted order this way:
public static function buildPositionSort():Sort
{
var dataSortField:SortField = new SortField();
dataSortField.name = "position";
dataSortField.numeric = true;
dataSortField.descending = false;
var sort:Sort = new Sort();
sort.fields = [ dataSortField ];
return sort;
}
/**
* This method is used to create a clone of ArrayCollection, because sorting does not
* actually change the physical ordering of the items.
*/
public static function createSortedArrayCollectionCopy(source:ArrayCollection):ArrayCollection
{
var target:ArrayCollection = new ArrayCollection();
source.sort = buildPositionSort();
source.refresh();
for each (var item:Object in source)
{
if (item.children != null) item.children = createSortedArrayCollectionCopy(item.children);
target.addItem(item);
}
return target;
}

Flex Advanced Data Grid w/ hierarchical data: How to access currentTarget fields on dragdrop event?

So this is driving me crazy. I've got an advanced data grid with a dataprovider that's an array collection w/ hierarchical data. Each object (including the children) has an id field. I'm trying to drag and drop data from within the ADG. When this happens, I need to grab the id off the drop target and change the dragged object's parentid field. Here's what I've got:
public function topAccountsGrid_dragDropHandler(event:DragEvent):void{
//In this function, you need to make the move, update the field in salesforce, and refresh the salesforce data...
if(checkActivateAccountManageMode.selected == true) {
var dragObj:Array = event.dragSource.dataForFormat("treeDataGridItems") as Array;
var newParentId:String = event.currentTarget['Id'];
dragObj[0].ParentId = newParentId;
} else {
return;
}
app.wrapper.save(dragObj[0],
new mx.rpc.Responder(
function():void {
refreshData();
},
function():void{_status = "apex error!";}
)
);
}
I can access the data I'm draggin (hence changing parentId) but not the currentTarget. I think the hierarchical data is part of the problem, but I can't find much in the documentation? Any thoughts?
event.currentTarget is not a node, it's the ADG itself. However, it's quite easy to get the information you want, since the ADG stores that data internally (as mx_internal).
I'm using the following code snippets (tested with Flex SDK 4.1) in a dragOver handler, but I guess it will work in a dragDrop handler too.
protected function myGrid_dragDropHandler(event:DragEvent):void
{
// Get the dragged items. This could either be an Array, a Vector or NULL.
var draggedItems:Object = getDraggedItems(event.dragSource);
if (!draggedItems)
return;
// That's our ADG where the event handler is registered.
var dropTarget:AdvancedDataGrid = AdvancedDataGrid(event.currentTarget);
// Get the internal information about the dropTarget from the ADG.
var dropData:Object = mx_internal::dropTarget._dropData;
// In case the dataProvider is hierarchical, get the internal hierarchicalData aka rootModel.
var hierarchicalData:IHierarchicalData = dropTarget.mx_internal::_rootModel;
var targetParent:Object = null;
// If it's a hierarchical data structure and the dropData could be retrieved
// then get the parent node to which the draggedItems are going to be added.
if (hierarchicalData && dropData)
targetParent = dropData.parent;
for each (var draggedItem:Object in draggedItems)
{
// do something with the draggedItem
}
}
protected function getDraggedItems(dragSource:DragSource):Object
{
if (dragSource.hasFormat("treeDataGridItems"))
return dragSource.dataForFormat("treeDataGridItems") as Array;
if (dragSource.hasFormat("items"))
return dragSource.dataForFormat("items") as Array;
if (dragSource.hasFormat("itemsByIndex"))
return dragSource.dataForFormat("itemsByIndex") as Vector.<Object>;
return null;
}
var dropData:Object = mx_internal::dropTarget._dropData;
should be
var dropData:Object = dropTarget.mx_internal::_dropData;
Try this.

Combine/merge Dynamic Objects in AS3

I have 2 dynamic objects and I want to build one to contain all the properties:
var o1:Object = {prop1:val1,prop2:val2,prop3:val3};
var o2:Object = {prop3:val3a,prop4:val4};
and I need to obtain a third object that looks like that:
{prop1:val1, prop2:val2, prop3:val3a, prop4:val4};
Basically I need a way to iterate through the object properties and to add new properties to the third object. I have to mention I'm quite new to AS3/Flash/Flex.
First question, do you really mean to have prop3 in both objects? you will need to decide what to do in case of a collision like that, which object has precedence.
Secondly, check out the introspection apis: http://livedocs.adobe.com/flex/3/html/help.html?content=usingas_8.html
something like this should work:
public function mergeDynamicObjects ( objectA:Object, objectB:Object ) : Object
{
var objectC:Object = new Object();
var p:String;
for (p in objectA) {
objectC[p] = objectA[p];
}
for (p in objectB) {
objectC[p] = objectB[p];
}
return objectC;
}
If the property exists in A and B, B's will overwrite A's. Also note that if the values of a property is an object, it will pass a reference, not a copy of the value. You might need to clone the object in those cases, depending on your needs.
Note: I haven't actually tested the above, but it should be close. Let me know if it doesn't work.
Updated to fix the errors. Glad it works for you though.
You can dynamically access/set properties on objects with the index operator. The for loop will itterate over the property names, so if you put it all together, the following test passes:
[Test]
public function merge_objects():void {
var o1:Object = {prop1:"one", prop2:"two", prop3:"three"};
var o2:Object = {prop3:"threeA", prop4:"four"};
var o3:Object = new Object();
for (var prop in o1) o3[prop] = o1[prop];
for (var prop in o2) o3[prop] = o2[prop];
assertThat(o3.prop1, equalTo("one"));
assertThat(o3.prop2, equalTo("two"));
assertThat(o3.prop3, equalTo("threeA"));
assertThat(o3.prop4, equalTo("four"));
}
you can iterate over the object properties like:
var obj1:Object = new Object();
for(var str:String in obj2){
obj1[str] = "any value"; // insert the property from obj2 to obj1
}

Flex: Get an item from a AdvancedDataGrid given an index

I've got a subclass of AdvancedDataGrid showing a tree-like data structure. How can I, given the index returned by calculateDropIndex, get the item at that index?
After reading through reams of code, it seems like the least terrible way is:
var oldSelectedIndex:int = this.selectedIndex;
var mouseOverIndex:int = this.calculateDropIndex(event);
this.selectedItem = mouseOverIndex;
var item:* = this.selectedItem;
this.selectedIndex = oldSelectedIndex;
The other option seems to be tinkering around with the iterator property... But, judging by the way I've seen it used, that will get pretty harry pretty quickly too.
So, how can I get the item at a particular index in an advanced datagrid without going insane?
You could try:
// Get the dropIndex from the dragEvent
var index:int = this.calculateDropIndex(event);
// Get the itemRenderer from the index
var renderer:IListItemRenderer = this.indexToItemRenderer(index);
// Get your item from the data property of the itemRenderer
var item:Object = renderer.data;
this.dataProvider.getItemAt(calculateDropIndex(event));
Looking through the docs, it seems you may be able to use openNodes, which returns an Array of all open nodes, which should correspond with your index?
this.dataProvider.openNodes[calculateDropIndex(event)];

Resources