I have a Flex question. I was wondering, if I have two list boxes, and I want to drag and drop between both of them, how do I prevent the user from dragging onto the same list (thus duplicating the item? I cannot have a situation where that is the case. Thanks guys
Haven't tested it but I guess something like this should work:
Listen to dragStart event on both lists and set a source variable depending on event.target. Now listen to the dragDrop event on both lists and call event.preventDefault() if the source is same as the target.
Here are some simple functions I made while building a working application I used to wrap my head around flex drag and drop. I was looking for a way to have multiple lists with drag and drop functionality that would not interfere with each other. Also, I didn't want to deal with copying list data around.
private function onlyAllowMoveDragOverHandler(event:DragEvent):void {
event.preventDefault();
event.currentTarget.showDropFeedback(event);
DragManager.showFeedback(DragManager.MOVE);
}
private function allowDropOnlyIfInitiatorEqualsComponent(event:DragEvent, component:IUIComponent):void {
event.preventDefault();
if (event.dragInitiator == component) {
DragManager.acceptDragDrop(event.target as IUIComponent);
}
else {
DragManager.showFeedback(DragManager.NONE);
}
}
And I in use in my mxml:
<mx:List
x="10"
y="170"
id="availableLangsList"
dataProvider="{availableLangs}"
width="100"
height="200"
dragEnabled="true"
dragMoveEnabled="true"
dropEnabled="true"
dragOver="onlyAllowMoveDragOverHandler(event);"
dragEnter="allowDropOnlyIfInitiatorEqualsComponent(event, selectedLangsList);"
dragComplete="selectedLangs.refresh();"
/>
<mx:Label x="129" y="153" text="list 4"/>
<mx:List
x="129"
y="170"
id="selectedLangsList"
dataProvider="{selectedLangs}"
width="100"
height="200"
dragEnabled="true"
dragMoveEnabled="true"
dropEnabled="true"
dragOver="onlyAllowMoveDragOverHandler(event);"
dragEnter="allowDropOnlyIfInitiatorEqualsComponent(event, availableLangsList);"
dragComplete="availableLangs.refresh();"
/>
I found the solution, which im not sure would work for anyone else. I basically had in my two lists:
`
<mx:List id="srcList" dataProvider="{_source}"
allowMultipleSelection="true"
enabled="{enabled}"
labelField="{labelField}"
iconFunction="iconFunction"
dragEnabled="true"
dropEnabled="true"
dragDrop="doDragDrop(event);"
width="100%"
height="100%"
/>
</mx:VBox>
<mx:VBox paddingTop="50">
<mx:Button label="->" enabled="{enabled}" click="add()"/>
<mx:Button label="<-" enabled="{enabled}" click="rem()"/>
</mx:VBox>
<mx:VBox width="100%" height="100%">
<mx:Label text="{right_col_heading}" />
<mx:List id="dstList" dataProvider="{_destination}"
allowMultipleSelection="true"
enabled="{enabled}"
dragEnabled="true"
dropEnabled="true"
dragDrop="doDragDrop(event);"
width="100%"
height="100%"
labelField="{labelField}"
iconFunction="iconFunction"
verticalAlign="center"
/>`
I basically added a dragMoveEnabled = "true" to both lists and now basically not re-add to the same list an item of itself, but just move the order (which doesnt matter to me as its a soap send and the back-logic would put it in the correct order anyway).
In my case, I used a HashCollection (which extends ArrayCollection) [just google it, you'll find the component]. The dataprovider is bind to this has collection. You would add items to the collection with: dataprovider.put (key, object) instead of dataprovider.addItem(object).
The "hash" will ensure uniqueness in the collection. SO, even if the user drag-drop something that already exists in the hash, the original value would get replaced with the new object (but it wouldn't matter because it's the same value).
The "key", however, must be unique.... otherwise, the hash idea won't work.
Thanks Brice, Those functions were helpful.
For them to work in Spark Lists just update the first function as follows with createDropIndicator instead of showDropFeedback and stop passing the event.
private function onlyAllowMoveDragOverHandlerS(event:DragEvent):void {
event.preventDefault();
event.currentTarget.createDropIndicator();
DragManager.showFeedback(DragManager.MOVE);
}
Related
Flex Experts,
I am a newbie here. I am trying to create a conditional tool tip for my contact detail screen. If the contact has 'special-roles' like 'ABC, XYZ' then the tool tip should appear otherwise not. We have AdvancedDataGridColumn and using dataTipFunction to display the tool tip.
I am passing, values of roles & 'special-roles' in dataProvider for all contact objects.
I need to write tool-tip condition for every row(every contact). I am unable to write the condition in dataTipFunction as it has access to only 'item' object. I tried trace and Alert on 'item', but screen goes blank.
I have also tried "showDataTips" which accepts boolean but to no avail. I need a way to pass current row values (all columns) to a function from AdvancedDataGridColumn.
Here is the snapshot of my code. Any help is very much appreciated :)
<view:CopyPasteDataGrid id="contactsHolder"
width="100%"
height="100%"
displayDisclosureIcon="true"
variableRowHeight="true"
defaultLeafIcon="{null}"
folderClosedIcon="{null}"
folderOpenIcon="{null}"
useRollOver="true"
selectable="true"
styleName="PortfolioAdvanced"
designViewDataType="tree"
horizontalCenter="true"
verticalGridLines="false"
horizontalScrollPolicy="off"
dataProvider="{contactDetails}"
headerRelease="onHeaderRelease(event)">
<view:columns>
<mx:AdvancedDataGridColumn dataField="lastName"
sortCompareFunction="lastNameCompare"
headerText="{ApplicationConstants.LAST_NAME_HEADER}"
showDataTips="true" dataTipFunction="buildToolTipForContact"
width="100"/>
<view:rendererProviders>
<mx:AdvancedDataGridRendererProvider dataField="projectContacts"
renderer="com.ihg.hom.common.view.components.render.ContactsRenderer"
columnIndex="0"
columnSpan="0"/>
</view:rendererProviders>
</view:CopyPasteDataGrid>
private function buildToolTipForContact(item:Object):String
{
var myString:String="";
return "hello";
}
I have resolved it using different approach. I used label function. It has access to column object and based current object roles I am setting tool tip. Here is the code for reference.
<mx:AdvancedDataGridColumn dataField="role" sortable="{isRoleSortable}"
labelFunction="getRole"
headerText="Role" sortCompareFunction="sortRole" showDataTips="true" dataTipFunction="buildToolTipForContact"
width="120"/>
private var hasRole:Boolean=false;
private function getRole(item:Object):String
{
// extra code
if(currentRoles.indexOf(specialRole)!=-1)
{
hasRole=true;
}
}
private function buildToolTipForContact(item:Object):String
{
var myStr:String="";
if(hasRole){
myStr="Special Role";
}
return myStr;
}
I am using a Repeater in an Accordian which does not appear to see a single element in userArray. If I add another entry to userArray then the Repeater works fine.
Thoughts??
private function currUsersServiceHandler(event:ResultEvent):void{
if (event.result.currentUsers != null)
{
if (event.result.currentUsers.user is ArrayCollection) // if more than one elements are present
{
usersArray = event.result.currentUsers.user;
}
else if (event.result.currentUsers is ObjectProxy)
{ //FIXIT usersArray populate by following line has some issue
usersArray = new ArrayCollection(ArrayUtil.toArray(event.result.currentUsers));
}
}
}
<mx:HTTPService id="currUsersService" url="currUsers.xml" result="currUsersServiceHandler(event)"/>
<mx:Accordion includeIn="UserList" x="10" y="10" width="554" height="242" >
<mx:Repeater id="rep" dataProvider="{usersArray}">
<mx:Canvas width="100%" height="100%" label="{rep.currentItem.firstName}" >
<mx:HBox>
<s:Label text="{rep.currentItem.firstName}"/>
<s:Label text="{rep.currentItem.lastName}"/>
<mx:/HBox>
</mx:Canvas>
</mx:Repeater>
</mx:Accordian>
Edit:
There is another thing I have just noticed i.e. that the accordian does show a single tab (when Array has a single element) but it's not labeled with the first name which I am setting. If I enter another user, two tabs appear and both are labeled with names I am setting. The first tab appears labeled too then.
It makes no sense to me that this would not work with 1 item in the dataProvider, but would work with two.
That said, tThis approach strikes me as convoluted and I tend to stay away from using repeaters at all. I would suggest a different approach.
First create a component to display your data. You can reuse you're existing code. Conceptually something like this:
<mx:Canvas width="100%" height="100%" >
<mx:Script><[[
public var user : Object;
]]></mx:Script>
<mx:HBox>
<s:Label text="{user.firstName}"/>
<s:Label text="{user.lastName}"/>
<mx:/HBox>
</mx:Canvas>
Then in your original component create the new instance of the component in ActionScript:
for each(var myUserObject : Object in usersArray){
var newUserDisplayObject : UserDisplayObject = new UserDisplayObject();
newUserDisplayObject.user = myUserObject;
newUserDisplayObject.label = myUserObject.firstName
accordian.addChild(newUserDisplayObject);
}
First of all I wanted to thank in advance to everyone that reads this post.
I'm having a problem when adding a child to a VBox component. Is it not the same thing?:
List Item:
<mx:Script>
<![CDATA[
public function addChildren():void {
var f:FaqItem=new FaqItem();
f.id="newUsersAssistance";
this.cont.addChild(f);
}
]]>
</mx:Script>
<mx:VBox id="cont" width="100%" borderThickness="0" verticalGap="0"/>
and:
<mx:VBox id="cont" width="100%" borderThickness="0" verticalGap="0">
<view:FaqItem id="newUsersAssistance" />
</mx:VBox>
I am injecting two properties (question and answer) to the FaqItem component from an auxiliar file (using BabelFX) that depends on the id of the FaqItem, but it is only working when I choose the second option. If I use the first option, I get a child but with the text in question and answer fields is empty. I need to use the first option.
Is there anything I am doing wrong?
Thanks again for everything
Kind Regards
i don't think you will be able use the id property of the dynamically added component to do Injection. I suggest you keep some bindable variables to bind the value to the dynamic FaqItem.
<mx:DataGrid x="10" y="10" width="180" height="302" id="dgActions" dataProvider="{actionCollection}">
<mx:columns>
<mx:DataGridColumn headerText="Action" dataField="name"/>
<mx:DataGridColumn headerText="" dataField="setting" width="30" rendererIsEditor="true">
<mx:itemRenderer >
<mx:Component>
<mx:Box width="100%" height="100%" horizontalAlign="center" verticalAlign="middle">
<mx:CheckBox selected="{data.setting}" click="setActionSetting()">
<mx:Script>
<![CDATA[
private function setActionSetting(){
data.setting = String(this.selected);
}
]]>
</mx:Script>
</mx:CheckBox>
</mx:Box>
</mx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</mx:columns>
</mx:DataGrid>
For some reason I'm getting an error at the data.setting= String(this.selected) line which says "Access to possibly indefined property selected through a reference with static type".
[edit] The solution to the above problem (albeit not the entire mess) was that once you're inside a <mx:Component> tag you are within the scope of said component. To get access to the script and nodes outside this component you have to use the outerDocument object.
[end edit]
I'm not sure what it's expecting, I'm assuming that it's going to pass the true/false value of the selected(ness) of the checkbox into the method, but it appears not to understand what "this" is, in this context.
Am I doing something obviously wrong? All I want is for the data source to reflect the change in the status that it initially fed into the checkbox.
EDIT:
I just noticed that when I add trace('foo') to the function, it never writes anything back to the console. Is the checkbox's native behavior (and event capture) preventing it from bubbling past to my function?
Additionally, when I add references to objects outside in the rest of the document, it tells me it doesn't recognize them. I'm totally confused as to how Flex scopes things... any additional guidance or links to reference would be really handy.
this in this (ha) case is referring to the component renderer and not the surrounding class (or the checkbox, datagridcolumn, datagrid, etc). You are really better off breaking the renderer out into a real component so you won't be obfuscating the scope as much as when the inline component approach is used.
Peter Ent's series on itemRenderers is extremely useful and should explain everything you want to know on the subject.
If I had to guess "this" is the mx:Script element, try "parent.selected".
CheckBox.selected requires a Boolean value. The fact that you're setting data.setting to a String value tells me that data.setting is NOT a Boolean.
So, after a great deal of agony I have finally figured out how this all works....
Joel is on the right track, this doesn't refer to what you would hope it would refer to (namely the checkbox). Additionally, even if you pass this into the method FROM the checkbox node, it refers to the parent wrapper class and not the checkbox itself. So, the solution is to pass in the event, and then access its target, which FINALLY is the checkbox. And then you're home.
In other words...
<mx:CheckBox selected="{data.setting}" click="setActionSetting(event)">
<mx:Script>
<![CDATA[
private function setActionSetting(e:Event):void{
data.setting = e.target.selected;
trace("n=" + data.name + " set to " + data.setting);
//the name is the other piece of the data that I omitted for clarity
}
]]>
</mx:Script>
</mx:CheckBox>
I actually wanted to create an itemrenderer which will contain below code :
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="0" height="0" >
<mx:Label text="{data.xval}" />
<mx:Spacer width="100%" />
<mx:Image source="edit.gif" width="16" toolTip="Edit" />
</mx:HBox>
Now when i click on the image datgrid column should be editable, for that i am using a textinput as an itemeditor. Now i also want to highlight all those cell for this datagrid column which are having data more than 8 characters. I am able to do this separately using 2 different itemrenderers. but i want to get this all together. Can anyone pls help me to do this? Can we have multiple itemrenderer for any datagrid column?
Please let me know if my question is not clear to you?
Thanking you in advance.
One way to do it is to create a function that returns the name of the style that you want to use for highlighting, and call that function by databinding to the style property of your HBox. For example, if you had one css class named highlight and one named normal, you could use this function:
public function highlight(data:String):String
{
if(data.length >= 8)
{
return "highlight";
}
return "normal";
}
And call it like this:
<mx:HBox styleName="{highlight(data.xval)}">
...
</mx:HBox>