I have various (read lots of..) flex forms in my app, and I now want to create a system by which the user is notified that he hasn't saved if he modifies anything in the form...
now.. I don't want to rewrite every form I have.. is there some nice way to implement this?
Extending the TextInput (and others..) classes somehow?
thanks
This is not really thought through but should work.
You could create a custom component, let's call it FormWatcher which you would than put next to your Form. What the form watcher would do is wait for the CreationComplete event from the form.
So now, as we have the Form ready you can use the getChildren() method of the form to get all the FormItems in it. Than look inside each of them, and you will get TextInputs, Comboboxes, etc. to which you can add event listeners (as individual components), eg.
// THIS IS WHERE THE COMPONENT SHOULD START
protected function changeHandler(event:Event):void
{
trace ("something is dirty");
}
protected function startWatching(passTheFormHere:Form):void
{
for each (var O:Object in passTheFormHere.getChildren())
{
if (O is FormItem)
{
// Let's assume you only have a single child in one FormItem
// and always one child for simplicity
addChangeHandlerFor((O as FormItem).getChildAt(0));
}
}
}
protected function addChangeHandlerFor(someComponent:Object):void
{
// Most regular flex components send a Event.CHANGE event
// when their value is changing
// keep in mind you should check stuff, this is a simple example
(someComponent).addEventListener(Event.CHANGE,changeHandler);
}
// THIS IS WHERE THE COMPONENT SHOULD END
Just paste this code next to some form, and call the startWatching(nameOfYourForm), you should see the changeHandler is being called.
A few more notes:
1) You should clean up the event listeners once you're done.
2) I would create a component out of it so that you would use it like this:
<mx:Form id="form1" >
[...]
</mx:Form>
<FormWatcher form="{form1}" />
Where FormWatcher would have a Boolean var called "clean" or something.
3) The example is very simple, so it will only work for forms similiar to this one:
<mx:Form id="myForm" >
<mx:FormItem>
<mx:TextInput id="someComponent1" />
</mx:FormItem>
<mx:FormItem>
<mx:CheckBox id="someComponent2" />
</mx:FormItem>
<mx:FormItem>
<mx:TextArea id="someComponent3" />
</mx:FormItem>
</mx:Form>
You could go into the TextInput class (and others) and add that event listener and function, but then you would be changing the SDK itself, which is kind of a bad idea. I would create custom classes extending the ones your using and do a find/replace to make it faster.
Related
I have a simple DataGrid with data. Of one of the columns, I want to use a ComboBox to edit the field, instead of the standard edit box.
How do I do that? I have tried all kind of things I found on the internet, but they all fail in simply updating the value. I'd say it shouldn't be too hard to do this.
I'm actually in the process of doing this myself, and with the spark:DataGrid it actually gets a bit easier than halo - but both follow the same setup / architecture.
Start with:
spark.components.gridClasses.ComboBoxGridItemEditor;
Depending on the nature of your data setup and/or how prolific this kind of editing will be for your application, you can write it inline as most documentation will suggest within a <fx:component>, or simply subclass this (although behind the scenes these are the same thing - the later being much easier to reuse). The data for the combo in my scenario is a sub selection of a bigger parent object, so I chose to make it easier on myself and add an additional property dataField to mimic other renderer / editors - in what actually shows in just the cell itself (when not in editing mode).
A basic setup looks something more or less like this (at least mine does):
public class AccountComboEditor extends ComboBoxGridItemEditor
{
private _dataField:String;
public function AccountComboEditor()
{
super();
//note - typically you wouldn't do "logic" in the view but it's simplified as an example
addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
}
public function get dataField():String { return _dataField; }
public function set dataField(value:String):void
{
if (_dataField !=value) //dosomeadditionalvalidation();
_dataField = value;
}
override public function prepare():void
{
super.prepare();
if (data && dataField && comboBox) comboBox.labelField = data[dataField];
}
protected function onCreationComplete(event:FlexEvent):void
{
//now setup the dataProvider to your combo box -
//as a simple example mine comse out of a model
dataProvider = model.getCollection();
//this isn't done yet though - now you need a listener on the combo to know
//what item was selected, and then get that data_item (label) back onto this
//editor so it has something to show when the combo itself isn't in
//editor mode
}
}
So the real take away is to setup the labelField of the combobox, either internally in the subclass or externally if you need to expose it as an additional property.
The next part is to use this as part of the mx.core.ClassFactory for the actual data grid. A simple view would look like something similar:
<s:DataGrid>
<fx:Script>
private function getMyEditor(dataField:String):ClassFactory
{
var cf:ClassFactory = new ClassFactory(AccountComboEditor);
cf.properties = {dataField : dataField };
return cf;
}
</fx:Script>
<s:columns>
<mx:ArrayList>
<s:GridColumn itemEditor="{getMyEditor('some_data_property')}" />
</mx:ArrayList>
</s:columns>
</s:DataGrid>
This Creating item renderers... doc will give you more info.
I figured it out. I just wanted a simple drop down box, instead of a text-editing field.
The following code does want I want:
<mx:DataGridColumn dataField="type" headerText="Type" editorDataField="value">
<mx:itemEditor>
<fx:Component>
<mx:ComboBox>
<mx:dataProvider>
<fx:String>Gauge</fx:String>
<fx:String>Graph</fx:String>
<fx:String>Indicator</fx:String>
</mx:dataProvider>
</mx:ComboBox>
</fx:Component>
</mx:itemEditor>
</mx:DataGridColumn>
When I start a drag operation I would like to be able to highlight the possible valid Drop objects. For this I need to know when the drag operation starts and which items are being dragged. I am trying to do this using the dragStart, but the event.dragSource is null on this event.
I have this list:
<s:List
width="100%"
height="100%"
id="productsListing"
dragEnabled="true"
dataProvider="{products}"
dragStart="dragStartHandler(event);"
dragComplete="dragCompleteHandler(event);"
itemRenderer="views.productListed" />
And I have the listener as:
public function dragStartHandler(event:DragEvent):void {
var itemsVector:Vector.<Object> = event.dragSource.dataForFormat('itemsByIndex') as Vector.<Object>;
//Verify Items
//Highlight appropriated dropZones
}
Anyone have a good sugestion how to overcome this?
The problem here is that your dragStartHandler is taking higher precedence than the List components internal dragStartHandler - which is where the drag operation is started and the dragSource property created.
Suggestion, manually add your dragStartHandler with a lower precedence than the List components dragStartHandler method - looking at the code this needs to be less than -50.
MXML Code:
<s:List width="100%" height="100%"
id="productsListing"
dragEnabled="true"
dataProvider="{products}"
initialize="productsListing_initializeHandler(event)"
dragComplete="productsListing_dragCompleteHandler(event)"
itemRenderer="views.productListed"
/>
AS Code:
protected function productsListing_initializeHandler(event:FlexEvent):void
{
// Needs to be handled AFTER the List component has handled the event and attached the dragSource data, hence priority is -51
this.productsListing.addEventListener(DragEvent.DRAG_START, productsListing_dragStartHandler, false, -51, true);
}
protected function productsListing_dragStartHandler(event:DragEvent):void
{
// Your code here...
}
I hope you find that useful.
This is the in built solution for drag drop in flex
we can implement the Object Handles for all component
it is easy to drag, drop and resize.
refer this http://code.google.com/p/flex-object-handles/
When I create something like the following:
<mx:DataGrid id"myDataGrid"
itemEditBegin="myDataGrid_itemEditBeginHandler(event)" />
When does the event listener for "itemEditBegin" get added and removed? Is this essentially the same as:
<mx:DataGrid id="myDataGrid"
creationComplete="myDataGrid_creationCompleteHandler(event)" />
protected function myDataGrid_creationCompleteHandler(event:FlexEvent):void
{
this.myDataGrid.addEventListener(DataGridEvent.ITEM_EDIT_BEGIN,
this.myDataGrid_itemEditBeginHandler);
}
protected function myDataGrid_whatEventDispatcherGoesHere?Handler(event:FlexEvent):void
{
this.myDataGrid.removeEventListener(DataGridEvent.ITEM_EDIT_BEGIN,
this.myDataGrid_itemEditBeginHandler);
}
Basically, I'm wondering where I should add "myDataGrid.addEventListener" if I want to do it programmatically? Should it be in the creationComplete listener function of the object itself, or perhaps in the creationComplete listener function for whatever parent object it resides in?
If you're adding the event listener programmatically:
creationComplete handler of the object is a good place for it
If you're creating the dataGrid programmatically, just add it anytime after instantiating the object
Putting it the in the Parent's creationComplete handler adds unnecessary complexity to your code, and I wouldn't recommend it. However, it will work.
Good reference for object creation in Flex: http://www.mikaflex.com/?p=270
I could code what I want to achieve like this:
<mx:Button id="someButton" click="doRememberButton(someButton)" ... />
but would find it very helpful (I am putting together a rather large UI) if I could write:
<mx:Button click="doRememberButton(this)" ... />
Now, the obvious problem is that 'this' does not point to the Button, but to the main component defined by the file the code is in (e.g. VBox), yet it would be a great help if I had some reference to the 'current' MXML component..
Would anybody have a solution for this? Thanks!
Tom
Inline event handlers is really just wrapped code, so you may use the event object to get details of the dispatcher and other event information. Like so:
<mx:Button click="trace(event.target)" />
In your case, you'd have to change the signature of your event handler, e.g.:
private function doRememberButton(event:Event):void
{
...
}
And in the MXML code:
<mx:Button click="doRememberButton(event)" />
The target property of the event class is the original dispatcher of the event. There is also a currentTarget property which is the current target in the event chain. This relates to event bubbling. There is more information on this in Adobe LiveDocs
private function doRememberButton(ev: Event) {
//this gives your button
ev.currentTarget;
}
here is a solution more precisely the way u needed
<mx:Button id="someButton" click="doRememberButton(event.currentTarget as Button)" />
at the function:
private function doRememberButton(thisBtn:Button):void
{
...
}
that's it! :)
In Flex 3, buttons call their click handler when they are clicked on by the mouse, or when they have the focus and the user depresses the space bar.
Is there a straightforward way to cause Flex 3 buttons with focus to call their click handler when the user presses the enter key?
Sure, you could do something like this:
<mx:Script>
<![CDATA[
import mx.controls.Alert;
private function btn_click(event:MouseEvent):void
{
Alert.show("Clicked!");
}
private function btn_keyDown(event:KeyboardEvent):void
{
if (event.keyCode == Keyboard.ENTER)
btn.dispatchEvent(new MouseEvent(MouseEvent.CLICK));
}
]]>
</mx:Script>
<mx:Button id="btn" label="Click Me" click="btn_click(event)" keyDown="btn_keyDown(event)" />
... although I'm not a huge fan of dispatching events on objects outside of those objects. A cleaner approach might be to subclass Button, add the listeners and handlers inside your subclass, and then dispatch the click event from within that class. But this should help illustrate the point. Good luck!
For something like a login form, you need to actually use an mx:form - here's the code snippet that illustrates it:
<mx:Form defaultButton="{loadButton}">
<mx:TextInput id="feedURL" />
<mx:Button id="loadButton" label="Load" click="someHandler(event)" />
</mx:Form>
Enter the url and hit enter, bam, expected behavior.
Googled from here.
If you are submitting a form like a Login dialog or the like, the "enter" property on the TextField is a great solution:
<mx:TextInput displayAsPassword="true" id="wPassword" enter="handleLoginSubmit()"/>
Even better
private function setValidationFocus(formObject:Object):void
{
formObject.setFocus();
formObject.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OUT)); // sneaky sneaky
formObject.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OVER));
}
You can also add the KEY_DOWN listener in Christian's answer to the button itself. Just make sure you call stopImmediatePropagation. In this example I let any key cause the button action. And I am using the same handler so I allow any "Event" type. You could use different "cancelClick" handlers.
protected function cancelClick(e:Event = null):void{
this.dispatchEvent(new Event(Event.CANCEL)); // do component action
e.stopImmediatePropagation();
}
override protected function partAdded(partName:String, instance:Object):void {
super.partAdded(partName,instance);
switch(instance){
case cancel:
cancel.addEventListener(MouseEvent.CLICK,cancelClick);
cancel.addEventListener(KeyboardEvent.KEY_DOWN,cancelClick);
}
}
Try the "buttonDown" event; it may be dispatched in either case. Otherwise you are probably looking at using "keyDown" and checking the key that was pressed.
I've been lookind at the same problem but the answer from Christian Nunciato seem's to me to be the best.
One more thing to add to his solution, I think that the bubbling, flag in btn.dispatchEvent(new MouseEvent(MouseEvent.CLICK)), should be set to false (btn.dispatchEvent(new MouseEvent(MouseEvent.CLICK, false))) because
the event should be isolated on the button.