How can I clear a 'textfield' - tornadofx

I'm trying to write text to a textfield, clear the text and the write a new text. I can't get rid of the old text. The new is written on the old so I see them both. I'm using choosefile and trying to show the selected file in a textfield so I can confirm the selection.
class TestView : View("My View") {
var tf: TextField by singleAssign()
override val root = BorderPane()
init {
with(root) {
center = form {
fieldset("Main") {
field("File") {
vbox {
tf = textfield()
tf.text("678")
tf.clear()
tf.text("999")
}
}
}
}
}
}
}
I expected to to see '999' in the textfield but I see both 678 and 999 at the same place.

You are actually calling the text() builder on the textfield, so you're basically creating two text elements inside the text field. I think you should take a step back and read the guide before you go further :) Here is a cleaned up version of your code:
class TestView : View("My View") {
val filename = SimpleStringProperty()
override val root = borderpane {
center {
form {
fieldset("Main") {
field("File") {
textfield(filename)
button("Browse...").action {
val filters = arrayOf(FileChooser.ExtensionFilter("All Files", "*.*"))
chooseFile("Choose file...", filters).firstOrNull()?.let {
filename.value = it.path
}
}
}
}
}
}
}
}

Related

Handling commands from the viewmodel to the UI

The peculiarity of this application is that every time a user does something (except common things like typing) the application must check with an authority that they are indeed allowed to perform that action.
For example, let us say that the user wishes to see their profile (which is on the top bar)
the Composable screen looks something like this:
#Composable
fun HomeScreen(
navController: NavController,
vm: HomeViewModel = hiltViewModel()
) {
val state = vm.state.value
val scaffoldState = rememberScaffoldState()
HomeScreen(state, scaffoldState, vm::process)
}
#Composable
fun HomeScreen(state: HomeState, scaffoldState: ScaffoldState, event: (HomeEvent) -> Unit) {
Scaffold(
scaffoldState = scaffoldState,
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = {
Text("Hello world")
},
actions = {
IconButton(onClick = {
event.invoke(HomeEvent.ShowProfile)
}) {
Icon(
painter = painterResource(id = R.drawable.ic_person),
contentDescription = stringResource(id = R.string.profile)
)
}
}
)
}
) {
}
}
the view model receives it like so:
#HiltViewModel
class HomeViewModel #Inject constructor(app: Application, private val checkAllowed: CheckAllowed): AndroidViewmodel(app) {
val state = mutableStateOf(HomeState.Idle)
fun process(event:HomeEvent) {
when(event) {
HomeEvent.ShowProfile -> {
state.value = HomeState.Loading
viewModelScope.launch {
try {
val allowed = checkAllowed(Permission.SeeProfile) //use case that checks if the action is allowed
if (allowed) {
} else {
}
} finally {
state.value = HomeState.Idle
}
}
}
}
}
}
I now have to send a command to the ui, to either show a snackbar with the error or navigate to the profile page.
I have read a number of articles saying that compose should have a state, and the correct way to do this is make a new state value, containing the response, and when the HomeScreen receives it , it will act appropriately and send a message back that it is ok
I assume something like this :
in the viewmodel
val command = mutableStateOf<HomeCommand>(HomeCommand.Idle)
fun commandExecuted() {
command.value = HomeCommand.Idle
}
and inside the HomeScreen
val command = vm.command.value
try {
when (command) {
is HomeCommand.ShowProfile -> navController.navigate("profile_screen")
is HomeCommand.ShowSnackbar -> scaffoldState.snackbarHostState.showSnackbar(command.message, "Dismiss", SnackbarDuration.Indefinite)
}
}finally {
vm.commandExecuted()
}
but the way I did it is using flows like so:
inside the viewmodel:
private val _commands = MutableSharedFlow<HomeCommand>(0, 10, BufferOverflow.DROP_LATEST)
val commands: Flow<HomeCommand> = _commands
and inside the HomeScreen:
LaunchedEffect(key1 = vm) {
this#ExecuteCommands.commands.collectLatest { command ->
when (command) {
is HomeCommand.ShowProfile -> navController.navigate("profile_screen")
is HomeCommand.ShowSnackbar -> scaffoldState.snackbarHostState.showSnackbar(command.message, "Dismiss", SnackbarDuration.Indefinite)
}
}
This seems to work, but I am afraid there may be a memory leak or something I'm missing that could cause problems
Is my approach correct? Should I change it to state as in the first example? can I make it better somehow?

How to change particular property value of a class in a LiveData<List<T>> (in my case LiveData<List<Item>>) using MediatorLiveData

The Item.kt class is
#Entity(tableName = "item")
class Item(
val id: Long,
val title: String,
) {
#Ignore
var selection: Boolean = false
}
Then i make a query to get all the items in the table ,it return
LiveData<List<Item>>
Then in the viewModel i want to apply selection(true) accordig to the Mutablelivedata selectionId, the selection id contain MutableLiveData<Long> (it contain an id in the LiveData<List<Item>>)
The MyViewModel.kt code is look like this
class MyViewModel(val repository: Repository) : ViewModel() {
..........
......
val selectionId: MutableLiveData<Long> by lazy {
MutableLiveData<Long>()
}
fun setSelectionId(id: Long) {
selectionId.postValue(id)
}
..........
......
val itemLiveList: LiveData<List<Item>> = liveData(Dispatchers.IO) {
emitSource(repository.getItems())
}
}
If it is an List<Item> i can do somethig like this
val ItemWithSelection: List<Item> = repository.getItems().apply {
this.forEach {
if (it.id == selectionId) {
it.selection = true
}
}
}
but i don't know how to achieve this using Mediator LiveData . Please help me
I don't understand everything in your code, for example I have never seen a function called liveData(CoroutineDispatcher). But do you mean you want something like this?
val listWithoutSelection = liveData(Dispatchers.IO) {
emitSource(repository.getItems())
}
val listWithSelection = MediatorLiveData<List<Item>>().apply {
addSource(listWithoutSelection) { updateListSelection() }
addSource(selectionId) { updateListSelection() }
}
fun updateListSelection() {
listWithSelection.value = listWithoutSelection.value?.map {
if (it.id == selectionId.value)
it.copyWithSelection(true)
else
it
}
}
The copyWithSelection could be easily done with Kotlin data classes. It is not needed dependent on whether you want to modify the object you get from the database. If you only use that object here, you could just always reset the selection of the others to false and then you can keep the object and you don't need a copy.

Eureka MultivaluedSection don't show add or delete icons

I am using the Eureka forms library in Swift but don't appears add or delete icons
This is the code:
+++
MultivaluedSection(multivaluedOptions: [.Reorder, .Insert, .Delete],
header: "Directions",
footer: "") {
$0.addButtonProvider = { section in
return ButtonRow(){
$0.title = "Add New item"
}
}
$0.multivaluedRowToInsertAt = { index in
return TextRow { row in
row.placeholder = "step"
}
}
$0 <<< TextRow { row in
row.placeholder = "step"
}
}
somebody knows if i am omitted a step or what do i need to do?
Thanks
I just add
tableView.isEditing = true
in viewDidLoad()

fragment for flowpane in TornadoFX

The TornadoFX docs describe using the ListCellFragment to bind each cell in a list control to each item model in a list. I am wondering how to do something similar in a flowpane. I'd like to use that kind of class to render a bunch of controls and an SVG drawing in each cell. (So it would replace the button component in the example code below and somehow bind the shapeItem model to it).
class LibraryView : View("Current Library") {
val shapeLibraryViewModel : LibraryViewModel by inject()
override val root = anchorpane{
flowpane {
bindChildren(shapeLibraryViewModel.libraryItemsProperty){
shapeItem -> button(shapeItem.nameProperty)
}
}
}
}
Since I don't see a pre-made class like the one for list view, perhaps I would need to create something similar to it...or maybe there's a more lightweight approach?
Using an ItemFragment is a bit overkill, since the itemProperty of the fragment will never change (a new fragment would be created for every item whenever the libraryItemsProperty change. However, if your view logic for each item is substantial, this approach will provide a clean way to separate and contain that logic, so it might be worth it. Here is a complete example you can use as a starting point.
class ShapeItemFragment : ItemFragment<ShapeItem>() {
val shapeModel = ShapeItemModel().bindTo(this)
override val root = stackpane {
label(shapeModel.name)
}
}
class ShapeItem(name: String) {
val nameProperty = SimpleStringProperty(name)
}
class ShapeItemModel : ItemViewModel<ShapeItem>() {
val name = bind(ShapeItem::nameProperty)
}
class LibraryViewModel : ViewModel() {
val libraryItemsProperty = SimpleListProperty<ShapeItem>(
listOf(
ShapeItem("Shape 1"),
ShapeItem("Shape 2")
).observable()
)
}
class LibraryView : View("Current Library") {
val shapeLibraryViewModel: LibraryViewModel by inject()
override val root = anchorpane {
flowpane {
bindChildren(shapeLibraryViewModel.libraryItemsProperty) { shapeItem ->
val itemFragment = find<ShapeItemFragment>()
itemFragment.itemProperty.value = shapeItem
itemFragment.root
}
}
}
}
A slightly lighter version would be to pass the parameter into your fragment manually and just extend Fragment:
class ShapeItemFragment : Fragment() {
val item: ShapeItem by param()
override val root = stackpane {
label(item.nameProperty)
}
}
You can still bind to changes to properties inside the ShapeItem, since the underlying item won't change (as seen from the ItemFragment) anyway.
Your bindChildren statement would then look like this:
bindChildren(shapeLibraryViewModel.libraryItemsProperty) { shapeItem ->
find<ShapeItemFragment>(ShapeItemFragment::item to shapeItem).root
}

how to implement autocomplete combobox in tornadofx

I have gone through the doc but no luck. Where as there is autocomplete in the github repo. Please provide a working example of tornadofx auto complete combobox?
In it's simplest form, you just call makeAutocompletable() on a ComboBox. Here is a complete view with a Form.
class MyView : View() {
val selectedFruit = SimpleStringProperty()
val fruits = listOf("Apple", "Banana", "Pear")
override val root = form {
fieldset {
field("Fruit") {
combobox(selectedFruit, fruits) {
makeAutocompletable()
}
}
}
}
}

Resources