Extending DropDownList to include an extra option - apache-flex

I want to extend DropDownList control to include an option for creating or editing the options. For example; for a list of projects in the dropdown list, there will be another option that says "Create new project..." or "Edit projects..." and this will be the last option in the list. When user selects this option, the selectedIndex or selectedItem will not change and corresponding action will be taken (for example a popup window shows up). This will be a convenient way for the end user.
Now I want this to work independent of the context and the class must be reusable. User will only specify the optionText and optionFunction to work this out. The basic structure of the class looks like this:
public class OptiveDropDownList extends DropDownList
{
private var _enableOption:Boolean;
private var _optionText:String;
private var _originalDataProvider:IList;
[Bindable] public var optionFunction:Function;
public function OptiveDropDownList()
{
super();
}
public function set optionText(value:String):void
{
_optionText = value;
dataProvider = _originalDataProvider;
}
public function set enableOption(value:Boolean):void
{
_enableOption = value;
dataProvider = _originalDataProvider;
}
public override function set dataProvider(value:IList):void
{
_originalDataProvider = value;
var dp:IList = null;
if(!value){
dp=new ArrayCollection(value.toArray());
if(_enableOption){
var opt:Object=new Object();
opt[labelField]=_optionText;
dp.addItem(opt);
}
}
super.dataProvider = dp;
}
[Bindable]
public override function get dataProvider():IList
{
return _originalDataProvider;
}
}
I hope my code is clear to understand, I am adding an extra object to the dataprovider for the option. Field names are self-explanatory.
Now my question is how to know whether the dataprovider's items have changed? Which functions should I override and how to do it. I have tried using a ChangeWatcher to watch the length property of the dataprovider, but it doesnt work if only an object in the dataprovider has changed. I need to capture these changes and update the view.
I also need to capture the selection and call optionFunction, preventing the default action not to give index out of bounds error.
Thanks in advance.

Just add an event listener to the original dataProvider. All implementations of IList should dispatch CollectionEvent.COLLECTION_CHANGE when the the list changes (e.g. add, remove or when an existing object in the list has been changed). In your event handler you can update the DropDownList's dataProvider accordingly.
By overriding the mx_internal method setSelectedIndex() you can adjust the selection according to your wishes. Take a look at the blog post "Disable selection on some items in a spark List" for some inspiration.

Related

In JavaFx, how to control focus of a custom control?

Suppose a JavaFX CustomControl node that contains, say, two TextFields.
If any of these TextFields has the focus, then CustomControl.isFocused() should return true. If none of them has focus, then CustomControl.isFocused() should return false.
How do I do that?
As your CustomControl uses composition, you can delegate to the focus properties of each TextField. Given two instances,
private final TextField tf1 = new TextField("One");
private final TextField tf2 = new TextField("Two");
The implementation of an instance method isFocused() is then straightforward:
private boolean isFocused() {
return tf1.isFocused() | tf2.isFocused();
}
Add focus listeners as shown here to see the effect.
tf1.focusedProperty().addListener((Observable o) -> {
System.out.println(isFocused());
});
tf2.focusedProperty().addListener((Observable o) -> {
System.out.println(isFocused());
});
This can't be done. The whole problem is that isFocused() is final in Node.
It seems you wanted to override isFocused() in CustomControl, but that is not possible for a final method and it would violate the notion of a single component having focus. As CustomControl is a composite, you'll need to manage focus internally. You may want to use a custom FocusModel as seen in ListView.
Try one line solution:
public BooleanBinding aggregatedFocusProperty() {
return Bindings.or(field1.focusedProperty(), field2.focusedProperty());
}
Now on a client side you may listen this aggregated focus property.

How to select an item of mx:ComboBox on the basis of substring entered through keyboard

I am using <mx:ComboBox /> and I want to select a matching item on the basis of string entered through keyboard. Currently, <mx:ComboBox /> selects the first matching item based on the first character only. I want this functionality to be customized. I am unable to find that KeyboardEvent listener which does the matching so that I can override it.
To do this yourself, you should look at the following bits and pieces of code below from the ComboBox and ListBase classes. ListBase is what the ComboBox component uses for it's drop down list.
The ComboBox appears to be deferring the keyboard input to the drop down list. It then listens for events from the drop down list to know when the selection has changed (as a result of keyboard or mouse input).
Flex components usually override a method called keyDownHandler() to process the keyboard input when they have focus. Starting there, we come across ComboBox line 2231:
// Redispatch the event to the dropdown
// and let its keyDownHandler() handle it.
dropdown.dispatchEvent(event.clone());
event.stopPropagation();
So now the keyDownHandler() in the drop down list will get executed. That method has a giant switch statement, where the default case statement on line 9197 of ListBase looks like this:
default:
{
if (findKey(event.charCode))
event.stopPropagation();
}
This is where the drop down list decides what to select based on keyboard input (when the input is not an arrow key or page up, etc.). The protected findKey() method simply calls the public findString() method to do this work.
So to override this behavior yourself:
extend the ListBase class and override the findKey() or findString() methods with your custom logic
extend ComboBox class and override the createChildren() method so you can instantiate your custom ListBase class instead of the default one.
Here is the class which I've used in order to make it work. searchStr is user inputted string which needs to be matched. If no dataprovider item gets matched to the searchStr, the overridden listener falls back to the default behaviour. I am using Timer to flush the inputted searchStr after 2 seconds. The possible drawback is that it is assuming the dataprovider to be a collection of String values. But you can modify it accordingly as the need may be.
public class CustomComboBox extends ComboBox
{
private var searchStr:String="";
private var ticker:Timer;
public function CustomComboBox()
{
super();
ticker = new Timer(2000);
ticker.addEventListener(TimerEvent.TIMER, resetSearchString);
}
override protected function keyDownHandler(event:KeyboardEvent):void
{
super.keyDownHandler(event);
// code to search items in the list based on user input.
// Earlier, the default behavior shows the matched items in the dropdown, based on first character only.
// user input is invisible to user.
if((event.charCode>=0x20 && event.charCode<=0x7E) || event.charCode==8) //Range of printable characters is 0x20[space] to 0x7E[~] in ASCII. 8 is ASCII code of [backspace].
{
ticker.reset();
ticker.start();
if(event.charCode==8)
{
if(searchStr=="")
return;
searchStr = searchStr.substr(0, searchStr.length-1);
}
else
{
searchStr += String.fromCharCode(event.charCode);
searchStr = searchStr.toLowerCase();
}
for each(var str:String in dataProvider)
{
if(str.toLowerCase().indexOf(searchStr, 0)>-1)
{
this.selectedItem = dropdown.selectedItem = str;
dropdown.scrollToIndex(dropdown.selectedIndex);
break;
}
}
}
}
/**
* reset the search string and reset the timer.
**/
private function resetSearchString(evt:TimerEvent):void
{
searchStr = "";
ticker.reset();
}
}

Enable/Disable FINISH button of wizard based on user input

I have created a wizard in AX 2012 using wizard wizard... Now i need to put 1 functionality i.e., to Enable or Disable FINISH button based on user input.
I have already tried these 3 ways but without success..
this.finishenabled() -- on SetupNavigation method of wizard class
finishenabled[formrun.tabidx()] = false -- on SetupNavigation method of wizard class
syswizard.finishenable(false, curtabidx(),false) - on Tabpage of wizard form
please do reply if anyone have a solution for this....
The Wizard class has a validate method in which you will do the following:
boolean validate()
{
if(SomeTestCondition)
{
return true;
}
return false;
}
According to Microsoft, this method does the following:
Used to validate user input, and called before the wizard is closed.
It returns false if user input is invalid. This will prevent the run method from being called when the user clicks the Finish button.
Wizard Class on MSDN
Additionally, you can use the textchanged() method on the field you want to validate (or if not text, you can use the changed method of the object).
if (this.text())
{
if (!sysWizard.isNextEnabled())
{
sysWizard.nextEnabled(true, sysWizard.curTab(), false);
}
}
else
{
if (sysWizard.isNextEnabled())
sysWizard.nextEnabled(false, sysWizard.curTab(), false);
}
Also from MSDN Enable Buttons
In SysWizard class the check to enable / disable the finishButton is inside a check for this.hasFinishButton() (see SysWizard.enableButtons).
I overcame this issue by overwriting the hasFinishButton() method on your wizard class and set the ret = true. This does mean however that your finish buttons will show in all steps, but you can hide this with other code if necessary.
The simplest way to enable/disable the Finish button on a Wizard form called from a SysWizard class is to retrieve the FormControl object from the FormRun object using the FormControlId and then set the Enabled property based on the your test condition, such as whether another FormControl contains a value. There are many ways to implement this. I'll provide two examples.
In the first example, all of the modifications are done on the Wizard Form.
A FormControl is used that can be called like any FormControl that has the AutoDeclaration property set to Yes.
In the second example, I'll override the finishEnabled() method on my Wizard class, so it behaves in the manner that was expected.
In each example, the formControl is found using the FormControlId which takes the control's label text ("Finish") as the argument. I found the correct Label ID by doing a "Lookup Label/Text" on "Finish" in the code editor and then selected the SYS label with "Label for Finish button in wizard" in the label's Description.
Example 1: FormControl object on Wizard Form:
In the Form classDeclaration add the following:
class FormRun extends ObjectRun
{
//FormControl objects used to get SysWizard Finish Button
FormControlId finishButtonId;
FormControl finishButton;
}
Initialize the new FormControl in the top level Form init() method:
void init()
{
super();
if (element.Args().caller())
{
sysWizard = element.Args().caller();
}
finishButtonId = sysWizard.formRun().controlId("#SYS302811");
finishButton = sysWizard.formRun().control(finishButtonId);
finishButton.enabled(false);
}
Now you can use the control like you would any other form control. In this case, I'm using the state of checkbox control named IsFinished in my WizardForm as the test condition and updating the FormControl state from the IsFinished.clicked() method:
public void clicked()
{
super();
//set FormControl state based on the current value of the checkbox
finishButton.enabled(this.checked());
}
*Example 2:*Override the finishEnabled() method in your Wizard class:
Note that you'll need to set the default values for the method parameters otherwise AX will throw a compile error because it doesn't match the signature from the base class. For some reason, AX doesn't properly create the method signature. Get rid of the default call to super and replace it with the code below:
public boolean finishEnabled(boolean _enabled = false,
int _idx = this.curTab(),
boolean _setfocus = false)
{
return this.formRun().control(this.formRun().controlId("#SYS302811")).enabled(_enabled);
}
Initialize the control value in the Form init() method:
void init()
{
super();
if (element.Args().caller())
{
sysWizard = element.Args().caller();
}
sysWizard.finishEnabled();
}
Call the class method when your controls are updated:
public void clicked()
{
super();
//set FormControl state based on the current value of the checkbox
sysWizard.finishEnabled(this.checked());
}

How do I make sure the text of an ActionScript TextInput is updated when the Object property defining that text is updated?

Not an easy question to decipher, so let me boil it down. I'm trying to convert an MXML component to an ActionScript Class. The component consists of a Form with a TextInput, TextArea, and two buttons - Save and Cancel, and a Validator for the TextInput, and other logic to handle events that occur. This component is currently extended by several other components.
Now, in the MXML component binding the TextInput text property to a property in an Object was very easy:
<mx:TextInput text="{_itemToEdit.name}" />
But in ActionScript, I'm creating the TextInput and setting the text property before the Object is set, and the TextInput is not being updated:
public var itemToEdit:Object = {};
private var nameInput:TextInput = new TextInput();
public function MyClass()
{
nameInput.text = itemToEdit.name;
}
How can I make sure that the TextInput text property is bound to the specified property in the Object?
Binding is all about firing change events. you'll need to modify your 'itemToEdit' class to be an EventDispatcher for this hack to work. here goes
//item to edit class
private var _name:String;
public function set name(value:String):void
{
_name = value;
dispatchEvent(new Event("NAME_CHANGED"));
}
//whatever the binding class is
private var _itemToEdit:ItemToEdit;
private var _textField:TextField;
public function set itemToEdit(value:ItemToEdit):void
{
if (_itemToEdit) removeEventListeners();
_itemToEdit = value;
if (_itemToEdit) addEventListeners();
}
private function addEventListeners():void
{
_itemToEdit.addEventListener("NAME_CHANGED", itemToEdit_nameChangedHandler);
itemToEditChangedHandler(null);
}
private function itemToEdit_nameChangedHandler(event:Event):void
{
_textField.text = _itemToEdit.name;
}
Obviously this was done just for speed, you'll need custom events and some better names etc, but this is the basic jist.
Apparently it's slightly more complex than a simple assignment to bind purely in AS, here's a couple tutorial/docs to show you how to pull it off.
http://cookbooks.adobe.com/index.cfm?event=showdetails&postId=6802
http://raghuonflex.wordpress.com/2007/08/30/binding-in-mxml-as/
Compile your MXML component with the -keep option. Examine the ActionScript code that was generated by mxmlc and do something similar.
You may also do it using the Proxy object - I blogged about it over here: http://flexblog.faratasystems.com/?p=433
If "itemToEdit" is a pure AS3 Object, then the binding probably doesn't work properly anyway. That is, it will work when the object is initially created, but any changes to "name" in the object won't be detected. (I could be wrong...haven't done extensive tests)
Anyway, your problem is easy to solve with getters/setters:
private var _itemToEdit:Object;
public function get itemToEdit():Object { return _itemToEdit; }
public function set itemToEdit(value:Objecy):void {
_itemToEdit = value;
nameInput.text = value.name;
}
Binding isn't necessary here.

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