I am quite new at Scala. I use a tableView that has the custom type "Constraint", and I need to iterate it by the row.
Here is where I have gotten so far:
class ConstraintView(val stage : Stage) {
var obs : ObservableBuffer[Constraint]=ObservableBuffer()
var listConst : List[ConstraintTrait]=List()
val checkColumn=new TableColumn[Constraint, java.lang.Boolean] {
text = ""
cellValueFactory=_.value.initialSelection.delegate
}
checkColumn.setCellFactory(CheckBoxTableCell.forTableColumn(checkColumn))
stage.title="Constraint Table View"
stage.scene=new Scene {
val tab=new TableView[Constraint](obs) {
editable=true
columns ++=List(checkColumn,
new TableColumn[Constraint, String] {
text="Constraint Name"
cellValueFactory=_.value.constraintNameProp
},
new TableColumn[Constraint, String] {
text = "Result"
cellValueFactory=_.value.resultProp
}
)
}
content=tab
}
In Java, you would do
for (Constraint c : tab.getItems()) {
// do something with c
}
or
tab.getItems().forEach(c -> {
// do something with c
});
I don't know Scala, but perhaps this is enough for you to be able to translate.
Related
I'm trying to build an app (macOS, but would be the same for iOS) that creates a number of grids, the outcome of which is to be shown in a second screen. For this, I'm sharing data across these screens, and I'm running into an issue here, I hope someone can help or point me in the right direction. I'll share a simplified version of the code below (working in Xcode 14.0.1)
The code creates a dictionary that can be shown in a grid, on which calculations can be done. The idea is then to add this grid, with some descriptive variables, into another dictionary
The building blocks of the grid are cells
Import Foundation
struct Cell: Comparable, Equatable, Identifiable, Hashable {
static func == (lhs: Cell, rhs: Cell) -> Bool {
lhs.randomVarOne == rhs.randomVarOne
}
var randomVarOne: Double
var randomVarTwo: Bool
// other vars omitted here
var id: Int { randomVarOne }
static func < (lhs: Cell, rhs: Cell) -> Bool {
return lhs.randomVarOne < rhs.randomVarOne
}
}
this is also where there are a bunch of funcs to calculate next neighbor cells in the grid etc
then the grid is defined in a class:
class Info: ObservableObject, Hashable {
static func == (lhs: Info, rhs: Info) -> Bool {
lhs.grid == rhs.grid
}
func hash(into hasher: inout Hasher) {
hasher.combine(grid)
}
#Published var grid = [Cell]()
var arrayTotal = 900
#Published var toBeUsedForTheGridCalculations: Double = 0.0
var toBeUsedToSetTheVarAbove: Double = 0.0
var rowTotalDouble: Double {sqrt(Double(arrayTotal)) }
var rowTotal: Int {
Int(rowTotalDouble) != 0 ? Int(rowTotalDouble) : 10 }
The class includes a func to create and populate the grid with Cells and add these Cells to the grid var. It also includes the formulas to do the calculations on the grid using a user input. The class did not seem to need an initializer.
This is the Scenario struct:
struct Scenario: Comparable, Equatable, Identifiable, Hashable {
static func == (lhs: Scenario, rhs: Scenario) -> Bool {
lhs.scenarioNumber == rhs.scenarioNumber
}
func hash(into hasher: inout Hasher) {
hasher.combine(scenarioNumber)
}
var scenarioNumber: Int
var date: Date
var thisIsOneSnapshot = [Info]()
var id: Int { scenarioNumber }
static func < (lhs: Scenario, rhs: Scenario) -> Bool {
return lhs.scenarioNumber < rhs.scenarioNumber
}
}
added hashable since it uses the Info class as an input.
Then there is the class showing the output overview
class OutputOverview: ObservableObject {
#Published var snapshot = [Scenario]()
// the class includes a formula of how to add the collection of cells (grid) and the additional variables to the snapshot dictionary. Again no initializer was necessary.
Now to go to the ContentView.
struct ContentView: View {
#Environment(\.openURL) var openURL
var scenarioNumberInput: Int = 0
var timeStampAssigned: Date = Date.now
#ObservedObject private var currentGrid: Info = Info()
#ObservedObject private var scenarios: Combinations = Combinations()
var usedForTheCalculations: Double = 0.0
var rows =
[
GridItem(.flexible()),
// whole list of GridItems, I do not know how to calculate these:
// var rows = Array(repeating: GridItem(.flexible()), count: currentGrid.rowTotal)
//gives error "Cannot use instance member 'currentGrid' within property initializer;
// property iunitializers run before 'self' is available
]
var body: some View {
GeometryReader { geometry in
VStack {
ScrollView {
LazyHGrid(rows: rows, spacing: 0) {
ForEach(0..<currentGrid.grid.count, id :\.self) { w in
let temp = currentGrid.grid[w].varThatAffectsFontColor
let temp2 = currentGrid.grid[w].varThatAffectsBackground
Text("\(currentGrid.grid[w].randomVarOne, specifier: "%.2f")")
.frame(width: 25, height: 25)
.border(.black)
.font(.system(size: 7))
.foregroundColor(Color(wordName: temp))
.background(Color(wordName: temp2))
}
}
.padding(.top)
}
VStack{
HStack {
Button("Start") {
}
// then some buttons to do the calculations
Button("Add to collection"){
scenarios.addScenario(numbering: scenarioNumberInput, timeStamp:
Date.now, collection: currentGrid.grid)
} // this should add the newly recalculated grid to the dictionary
Button("Go to Results") {
guard let url = URL(string: "myapp://scenario") else { return }
openURL(url)
} // to go to the screen showing the scenarios
Then the second View, the ScenarioView:
struct ScenarioView: View {
#State var selectedScenario = 1
#ObservedObject private var scenarios: OutputOverview
var pickerNumbers = [ 1, 2, 3, 4 , 5]
// this is to be linked to the number of scenarios completed,this code is not done yet.
var rows =
[
GridItem(.flexible()),
GridItem(.flexible()),
// similar list of GridItems here....
var body: some View {
Form {
Section {
Picker("Select a scenario", selection: $selectedScenario) {
ForEach(pickerNumbers, id: \.self) {
Text("\($0)")
}
}
}
Section {
ScrollView {
if let idx = scenarios.snapshot.firstIndex(where:
{$0.scenarioNumber == selectedScenario}) {
LazyHGrid(rows: rows, spacing: 0) {
ForEach(0..<scenarios.snapshot[idx].thisIsOneSnapshot.count,
id :\.self) { w in
let temp =
scenarios.snapshot[idx].thisIsOneSnapshot[w].varThatAffectsFontColor
let temp2 =
scenarios.snapshot[idx].thisIsOneSnapshot[w].varThatAffectsBackground
Text("\(scenarios.snapshot[idx].thisIsOneSnapshot[w].randomVarOne, specifier: "%.2f")")
.frame(width: 25, height: 25)
.border(.black)
.font(.system(size: 7))
.foregroundColor(Color(wordName: temp))
.background(Color(wordName: temp2))
}
}
}
}
}
}
}
}
Now while the above does not (for the moment..) give me error messages, I am not able to run the PreviewProvider in the second View. The main problem is in #main:
import SwiftUI
#main
struct ThisIsTheNameOfMyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.handlesExternalEvents(matching: ["main"])
WindowGroup("Scenarios") {
ScenarioView()
// error messages here: 'ScenarioView' initializer is inaccessible due to "private"
// protection level - I don't know what is set to private in ScenarioView that could
// cause this
// second error message: missing argument for parameter 'scenarios' in call
}
.handlesExternalEvents(matching: ["scenario"])
}
}
I am at a loss on how to solve these 2 error messages and would be very grateful for any tips or guidance. Apologies if this question is very long, I scanned many other forum questions and could not find any good answers.
I have tried adding pro forma data in #main as follows
#main
struct FloodModelScenarioViewerApp: App {
#State var scenarios = Scenario(scenarioNumber: 1, date: Date.now)
var body: some Scene {
WindowGroup {
ContentView()
}
.handlesExternalEvents(matching: ["main"])
WindowGroup("Scenarios") {
ScenarioView(scenarios: scenarios)
}
.handlesExternalEvents(matching: ["scenario"])
}
}
This still gives 2 error messages:
same issue with regards to ScenarioView initialiser being inaccessible due to being 'private'
Cannot convert value of type 'Scenario' to expected argument type 'OutputOverview'
Just remove the private from
#ObservedObject private var scenarios: OutputOverview
The value is coming from he parent so the parent needs access. So put
#StateObject private var scenarios: OutputOverview = .init()
in FloodModelScenarioViewerApp
#StateObject is for initializing ObservableObjects and #ObservedObject is for passing them around.
I don't know if your code will work after you read this question, and that's because there are many things to correct, but you can start with these:
In Cell, you shouldn't use an id that is a variable, this may cause inconsistent behavior. Use something like:
let id = UUID()
When you initialize ContentView, you can't use currentGrid inside a variable because currentGrid will not be available before all variables are initialized. Meaning, you are trying to initialize rows before currentGrid actually exists. You can try using the .onAppear modifier:
var rows = [GridItem]()
var body: some View {
GeometryReader { geometry in
// ... view code in here
}
.onAppear {
var rows = Array(repeating: GridItem(.flexible()), count: currentGrid.rowTotal)
}
}
This creates the view and, before showing it, the grid is set to its proper value.
The message 'ScenarioView' initializer is inaccessible due to "private" protection level seems clear: you must provide a value to to the variable scenarios (it doesn't have a default value) but it's marked as private. Remove private.
#ObservedObject var scenarios: OutputOverview
Then, remember to pass a value of type OutputOverview for the variable when you call the view:
ScenarioView(scenarios: aVariableOfTypeOutputOverview)
The type mismatch error you get inside the #main code is also clear - you have defined a variable of type Scenario:
#State var scenarios = Scenario(scenarioNumber: 1, date: Date.now)
but ScenarioView requires another type:
#ObservedObject private var scenarios: OutputOverview
One of them needs change for your code to work.
Hey I am very new to tornadofx struggeling with async loading of data for the treeview. I am loading categories from a rest endpoint, which I want to show in there.
It seems like there's no direct data binding to the children.
when using 'bindChildren' I can provide the observable list, but I have to convert them into Node's. which then would make the populate block kind of obsolete.
What's the recommended way of doing this? I cannot find anything about this.
// Category
interface Category<T : Category<T>> {
val id: String
val name: String
val subcategories: List<T>?
}
//default category:
class DefaultCategory(override val name: String) : Category<DefaultCategory> {
override val id: String = "default"
override val subcategories: List<DefaultCategory>? = null
}
//ViewModel
class CategoryViewModel : ViewModel() {
val sourceProperty = SimpleListProperty<Category<*>>()
fun loadData() {
// load items for treeview into 'newItems'
sourceProperty.value = newItems
}
}
// TreeViewFactoryMethod
private fun createTreeView(
listProperty: SimpleListProperty<Category<*>>
): TreeView<Category<*>> {
return treeview {
root = TreeItem(DefaultCategory("Categories"))
isShowRoot = false
root.isExpanded = true
root.children.forEach { it.isExpanded = true }
cellFormat { text = it.name }
populate { parent ->
when (parent) {
root -> listProperty.value
else -> parent.value.subcategories
}
}
}
}
Assuming that on a button click I call viewmodel.loadData(), I would expect the TreeView to update as soon as there's some new data. (If I would've found a way to bind)
I've never had to use bindChildren for TornadoFX before and your use of async isn't very relevant to what I think is your primary problem. So, admittedly, this question kind of confused me at first but I'm guessing you're just wondering why the list isn't appearing in your TreeView? I've made a test example with changes to make it work.
// Category
interface Category<T : Category<T>> {
val id: String
val name: String
val subcategories: List<T>?
}
//default category:
class DefaultCategory(override val name: String) : Category<DefaultCategory> {
override val id: String = "default"
override val subcategories: List<DefaultCategory>? = null
}
//Just a dummy category
class ChildCategory(override val name: String) : Category<ChildCategory> {
override val id = name
override val subcategories: List<ChildCategory>? = null
}
//ViewModel
class CategoryViewModel : ViewModel() {
//filled with dummy data
val sourceProperty = SimpleListProperty<Category<*>>(listOf(
ChildCategory("Categorya"),
ChildCategory("Categoryb"),
ChildCategory("Categoryc"),
ChildCategory("Categoryd")
).asObservable())
fun loadData() {
sourceProperty.asyncItems {
//items grabbed somehow
listOf(
ChildCategory("Category1"),
ChildCategory("Category2"),
ChildCategory("Category3"),
ChildCategory("Category4")
).asObservable()
}
}
}
class TestView : View() {
val model: CategoryViewModel by inject()
override val root = vbox(10) {
button("Refresh Items").action {
model.loadData()
}
add(createTreeView(model.sourceProperty))
}
// TreeViewFactoryMethod
private fun createTreeView(
listProperty: SimpleListProperty<Category<*>>
): TreeView<Category<*>> {
return treeview {
root = TreeItem(DefaultCategory("Categories"))
isShowRoot = false
root.isExpanded = true
root.children.forEach { it.isExpanded = true }
cellFormat { text = it.name }
populate { parent ->
when (parent) {
root -> listProperty
else -> parent.value.subcategories
}
}
}
}
}
There are 2 important distinctions that are important.
1. The more relevant distinction is that inside the populate block, root -> listProperty is used instead of root.listProperty.value. This will make your list appear. The reason is that a SimpleListProperty is not a list, it holds a list. So, yes, passing in a plain list is perfectly valid (like how you passed in the value of the list property). But now that means the tree view isn't listening to your property, just the list you passed in. With that in mind, I would be considerate over the categories' subcategory lists are implemented as well.
2. Secondly, notice the use of asyncItems in the ViewModel. This will perform whatever task asynchronously, then set the items to list on success. You can even add fail or cancel blocks to it. I'd recommend using this, as long/intensive operations aren't supposed to be performed on the UI thread.
I was taking a look at this :
tornadofx
and tried to expand on it with database connection and little more options, (not all of them make sense, but its just playing in a sandbox).
Even though table can be directly edited and the data will persist in database, i did try to do edit through text fields too. actual table editing would happen through different view and not table itself, as i said its just example.
Database used is Jetbrains Exposed.
object Categories : IntIdTable() {
val name = varchar("name", 64).uniqueIndex()
val description = varchar("description", 128)
}
class Category(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Category>(Categories)
var name by Categories.name
var description by Categories.description
override fun toString(): String {
return "Category(name=\"$name\", description=\"$description\")"
}
}
now controller looks something like this, functions are just rudimentary and picked as an example.
typealias ModelToDirtyState = Map.Entry<CategoryModel, TableColumnDirtyState<CategoryModel>>
class CategoryModel() : ItemViewModel<Category>() {
val name: SimpleStringProperty = bind(Category::name)
val description: SimpleStringProperty = bind(Category::description)
}
class DBController : Controller() {
val categories: ObservableList<CategoryModel> by lazy {
transaction {
SchemaUtils.create(Categories)
Category.all().map {
CategoryModel().apply {
item = it
}
}.observable()
}
}
init {
Database.connect(
"jdbc:mysql://localhost:3306/test", driver = "com.mysql.cj.jdbc.Driver",
user = "test", password = "test"
)
TransactionManager.manager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE
}
fun deleteCategory(model: CategoryModel) {
runAsync {
transaction {
model.item.delete()
}
}
categories.remove(model)
}
fun updateCategory(model: CategoryModel) {
transaction {
Categories.update {
model.commit()
}
}
}
fun commitDirty(modelDirtyMappings: Sequence<ModelToDirtyState>) {
transaction {
modelDirtyMappings.filter { it.value.isDirty }.forEach {
it.key.commit()
println(it.key)// commit value to database
it.value.commit() // clear dirty state
}
}
}
Just to quickly comment on controller, delete method works as "intended" however the update one does not, it does not work in sense that after using delete item is remove both from database and tableview(underlying list) itself, and when i do update its not, now i know the reason, i call remove manually on both database and list, now for update perhaps i could do change listener, or maybe tornadofx can do this for me, i just cant set it up to do it. Following code will make things clearer i think.
class CategoryEditor : View("Categories") {
val categoryModel: CategoryModel by inject()
val dbController: DBController by inject()
var categoryTable: TableViewEditModel<CategoryModel> by singleAssign()
var categories: ObservableList<CategoryModel> by singleAssign()
override val root = borderpane {
categories = dbController.categories
center = vbox {
buttonbar {
button("Commit") {
action {
dbController.commitDirty(categoryTable.items.asSequence())
}
}
button("Roll;back") {
action {
categoryTable.rollback()
}
}
// This model only works when i use categorytable.tableview.selected item, if i use categoryModel, list gets updated but not the view itself
// Question #1 how to use just categoryModel variable without need to use categorytable.tableview.selecteditem
button("Delete ") {
action {
val model = categoryTable.tableView.selectedItem
when (model) {
null -> return#action
else -> dbController.deleteCategory(model)
}
}
}
//And here no matter what i did i could not make the view update
button("Update") {
action {
when (categoryModel) {
null -> return#action
else -> dbController.updateCategory(categoryModel)
}
categoryTable.tableView.refresh()
}
}
}
tableview<CategoryModel> {
categoryTable = editModel
items = categories
enableCellEditing()
enableDirtyTracking()
onUserSelect() {
//open a dialog
}
//DOES WORK
categoryModel.rebindOnChange(this) { selectedItem ->
item = selectedItem?.item ?: CategoryModel().item
}
// Question #2. why bindSelected does not work, and i have to do it like above
//DOES NOT WORK
// bindSelected(categoryModel)
//
column("Name", CategoryModel::name).makeEditable()
column("Description", CategoryModel::description).makeEditable()
}
}
right = form {
fieldset {
field("Name") {
textfield(categoryModel.name)
}
}
fieldset {
field("Description") {
textfield(categoryModel.description)
}
}
button("ADD CATEGORY") {
action {
dbController.addCategory(categoryModel.name.value, categoryModel.description.value)
}
}
}
}
}
I apologize for huge amount of code, also in last code snipped i left questions in form of comments where i fail to achive desired results.
I am sure i am not properly binding code, i just dont see why, also i sometimes use one variable to update data, my declared one "categoryModel" and sometimes i use tableview.selecteditem, it just seems hacky and i cant seem to grasp way.
Thank you!
I'm new in JavaFX and have the following issue:
I have a tableview inside a BorderPane. I want it to focus on the last row/1st column when it's loaded. I have tried the following:
requestfocus()
scrollTo()
focusModel.focus()
selectionModel.select()
What happens is that the cell I want is indeed blue (as if it was selected) but the first cell has a blue border. So, when I try to use the arrow keys, the selected cell moves to the first row.
BTW, I'm using TornadoFX.
Any ideas?
Thanks in advance!
class CashflowTab : View() {
override val root: HBox by fxml()
private val mController : CashflowController by inject()
private val mainView : MainView by inject()
// Get the buttons
private val buttonCashflow : Button by fxid("btnCashflow")
init {
// Setup the buttons
buttonCashflow.action {
setupCashflowTable()
}
}
/** Displays the TableView for the Cashflow */
private fun setupCashflowTable() {
var initialFocus = true
// List of entries for the category ComboBox
val categoryList = mController.getCashFlowCategoryList()
// Create the table
val cashTable = tableview<CashEntry>(mController.getCashEntryList()) {
isEditable = true
column(Constants.COL_COUNT, CashEntry::countProperty)
column(Constants.COL_DATE, CashEntry::dateProperty).makeEditable(LocaleDateConverter())
column(Constants.COL_INCOME, CashEntry::incomeProperty).makeEditable(CurrencyConverter())
column(Constants.COL_EXPENSES, CashEntry::expensesProperty).makeEditable(CurrencyConverter())
column(Constants.COL_PROFIT, CashEntry::profitProperty).converter(CurrencyConverter())
column(Constants.COL_TOTAL_PROFIT, CashEntry::totalProfitProperty).converter(CurrencyConverter())
column(Constants.COL_COMMENTS, CashEntry::commentsProperty).makeEditable()
column(Constants.COL_CATEGORY, CashEntry::categoryProperty).useComboBox(categoryList)
// Scroll to and focus on the last cell on startup
if (initialFocus) {
val lastRow = mController.getCashEntryList().size - 1
requestFocus()
scrollTo(lastRow)
focusModel.focus(lastRow)
selectionModel.select(lastRow)
initialFocus = false
}
onEditCommit {entry ->
// Update the list
mController.updateCashEntryList(entry)
// Move to the next cell
requestFocus()
focusModel.focusRightCell()
#Suppress("UNCHECKED_CAST")
selectionModel.select(focusModel.focusedCell.row, focusModel.focusedCell.tableColumn as TableColumn<CashEntry, *>)
}
enableCellEditing()
// Enable edit on key typed
addEventHandler(KeyEvent.KEY_PRESSED) {keyEvent ->
if (keyEvent.code.isDigitKey || keyEvent.code.isLetterKey) {
if (editingCell == null) {
val currentSelectedCell = selectedCell
if (currentSelectedCell != null && currentSelectedCell.tableColumn.isEditable) {
edit(currentSelectedCell.row, currentSelectedCell.tableColumn)
}
}
}
}
}
// Add the table to the view
mainView.root.center = cashTable
cashTable.tableMenuButtonVisibleProperty()
// Ensure no other node can get focus
cashTable.focusedProperty().onChange {
val focusOwner = currentStage?.scene?.focusOwnerProperty()?.value
// Check if the focus owner is the table or a cell
if (focusOwner !is TableView<*> && focusOwner !is TextField) {
cashTable.requestFocus()
}
}
}
}
You should use
Platform.runLater(() -> {
requestFocus();
scrollTo(lastRow);
...
});
to update the GUI.
I'm attempting to search a combobox based on text entered via a keyboard event. The search is working and the correct result is being selected but I can't seem to get the scrollToIndex to find the correct item which should be the found result (i). It's scrolling to the last letter entered which I believe is the default behavior of a combobox. I think I'm referring to the event target incorrectly. Newbie tearing my hair out. Can you help? Thank you. Here's the function:
private function textin(event:KeyboardEvent):void
{
var combo:ComboBox = event.target as ComboBox;
var source:XMLListCollection = combo.dataProvider as XMLListCollection;
str += String.fromCharCode(event.charCode);
if (str=="") {
combo.selectedIndex = 0;
}
for (var i:int=0; i<source.length; i++) {
if ( source[i].#name.match(new RegExp("^" + str, "i")) ) {
combo.selectedIndex = i;
event.target.scrollToIndex(i);
break;
}
}
}
Control:
<mx:ComboBox keyDown="textin(event);" id="thislist" change="processForm();" dataProvider="{xmllist}"/>
If event.target is a mx.control.ComboBox then it doesn't have a scrollToIndex method, which is a method defined in mx.controls.ListBase, which the ComboBox doesn't inherit from. Check the api reference for the ComboBox. What exactly is the result you a you are trying to achieve here? If you set the selected index of a ComboBox it should display the item at that index.
EDIT: Try getting replacing event.target.scrollToIndex(i) (which should throw an error anyway) and replace it with event.stopImmediatePropagation(). This should prevent whatever the default key handler is from firing and overriding your event handler.
Here is a solution, based on Kerri's code and Ryan Lynch's suggestions. The credit goes to then.
It's working for me, so I will leave the complete code here for the future generations. :)
import com.utils.StringUtils;
import flash.events.KeyboardEvent;
import flash.events.TimerEvent;
import flash.utils.Timer;
import mx.collections.ArrayCollection;
import mx.controls.ComboBox;
public class ExtendedComboBox extends ComboBox
{
private var mSearchText : String = "";
private var mResetStringTimer : Timer;
public function ExtendedComboBox()
{
super();
mResetStringTimer = new Timer( 1000 );
mResetStringTimer.addEventListener( TimerEvent.TIMER, function() : void { mResetStringTimer.stop(); mSearchText = ""; } );
}
override protected function keyDownHandler( aEvent : KeyboardEvent ):void
{
if( aEvent.charCode < 32 )
{
super.keyDownHandler( aEvent );
return;
}
var lComboBox : ComboBox = aEvent.target as ComboBox;
var lDataProvider : ArrayCollection = lComboBox.dataProvider as ArrayCollection;
mSearchText += String.fromCharCode( aEvent.charCode );
if ( StringUtils.IsNullOrEmpty( mSearchText ) )
{
lComboBox.selectedIndex = 0;
aEvent.stopImmediatePropagation();
return;
}
if( mResetStringTimer.running )
mResetStringTimer.reset();
mResetStringTimer.start();
for ( var i : int = 0; i < lDataProvider.length; i++ )
{
if ( lDataProvider[i].label.match( new RegExp( "^" + mSearchText, "i") ) )
{
lComboBox.selectedIndex = i;
aEvent.stopImmediatePropagation();
break;
}
}
}
}
This solution expects an ArrayCollection as the dataProvider and a field named "label" to do the searching. You can create a variable to store the name of the field, and use it like this:
lDataProvider[i][FIELD_NAME_HERE].match( new RegExp( "^" + mSearchText, "i") )
Have fun!