<@U7QRFLHTK> You must never reference the person y...
# tornadofx
e
@anidotnet You must never reference the person you pass in, that defeats the whole purpose of the ViewModel and effectively stops you from assigning another person item to this viewmodel. Just do val name = bind(Person::name) instead. You can pass the person constructor param onto the ViewModel constructor, but I recommend to just remove it and do model.item = person if you need to assign the value manually. This avoids that whole confusion and also ensures that you have a noargs constructor so that you ViewModel also works with injection.
a
@edvin but it does not work either. My person object is only having var properties. It does not have tornadofx properties. So whenever I do a bind(Person::name), compiler complains.
Moreover, I was following the guide "Supporting Objects that Do Not Expose JavaFX Properties"
The reason I need the view model is to validate it in a form. If you can tell me how to write custom validator where we do not need to bind bidirectional property that is also serve the purpose.
e
@anidotnet ViewModel works with var properties as well. Post your code and the error message 🙂
a
class EmailAccount() : ChangeAware { var id: Long = LongIdGenerator.generate() var address: String = "" var name: String? = null var password: String = ""
this is the domain object
class EmailAccountModel : ItemViewModel<EmailAccount>() { val id = bind(EmailAccount::id) val name = bind(EmailAccount::name) val address = bind(EmailAccount::address) var password = bind(EmailAccount::password) }
this is model
here is the error - kotlin.TypeCastException: null cannot be cast to non-null type javafx.beans.property.Property<N>
e
When you do what? Complete code please 🙂
a
class GmailAccountInfoPage : LetterBoxWizardPage() { private val emailService: EmailService by di() private val account: EmailAccountModel by inject() override val previous: LetterBoxWizardPage? by lazy { find(EmailAccountWelcomePage::class) } init { account.item = EmailAccount() } override val page = vbox { vgrow = Priority.ALWAYS region { minHeight = 50.0 } hbox { alignment = Pos.CENTER form { alignment = Pos.CENTER fieldset("Provide GMail Account Details") { pane { minHeight = 40.0 } field("Name") { textfield(account.name) { minWidth = 300.0 required(message = "Account name is required") } } pane { minHeight = 20.0 } field("Email Address") { textfield(account.address) { minWidth = 300.0 required(message = "Email address is required") validator { if (it.isNullOrEmpty()) { error("Email address is required") } else if (!EmailValidator.getInstance().isValid(it)) { error("Not a valid email") } else null } } } pane { minHeight = 20.0 } field("Password") { passwordfield(account.password) { minWidth = 300.0 required(message = "Password is required") } } pane { minHeight = 60.0 } hbox { alignment = Pos.CENTER button { addClass(BaseLetterBoxTheme.borderlessButton) image = iconSet.finishImage text = "Finish" enableWhen { account.valid } action { runAsyncWithProgress { saveAccount() } success { finish() } fail { error("Error while adding account", it.message) } } } } account.validate(decorateErrors = true) } } } } private fun saveAccount() { account.commit() // emailService.validateAndCreateAccount() Thread.sleep(2000) throw Exception("Error could not connect") }
when I load this view, I am getting this error
e
That requires dependencies and other stuff. Hard to test.
Let's make a minimal sample.
a
give me 5 mins
e
I just made one, hang on 🙂
That's a bug in
bindMutableNullableField
. The guy who made it made the wrong assumption I see 🙂
I've committed a fix now.
a
great
I don't know if it is related or not.. Take this example
class PersonEditor : View("Person Editor") { val model : PersonModel by inject() override val root = form { fieldset("Edit person") { field("First Name") { textfield(model.firstName) } field("Last Name") { textfield(model.lastName) } button("Save") { enableWhen(model.dirty) action { save() } } button("Reset").action { model.rollback() } } } private fun save() { model.commit() println("Saving ${model.item.firstName} / ${model.item.lastName}") } } data class Person(val firstName: String, val lastName: String) class PersonModel : ItemViewModel<Person>() { val firstName = bind { item?.firstName?.toProperty() } val lastName = bind { item?.lastName?.toProperty() } } class MyTestApp: App(PersonEditor::class) fun main(args: Array<String>) { Application.launch(MyTestApp::class.java, *args) }
when you click save, you get a NPE
at line println("Saving ${model.item.firstName} / ${model.item.lastName}")
it looks like item is not being created
And if you modify the above code slightly (my use case) you get another error
class PersonEditor : View("Person Editor") { val model : PersonModel by inject() init { model.item = Person() } override val root = form { fieldset("Edit person") { field("First Name") { textfield(model.firstName) } field("Last Name") { textfield(model.lastName) } button("Save") { enableWhen(model.dirty) action { save() } } button("Reset").action { model.rollback() } } } private fun save() { model.commit() println("Saving ${model.item.firstName} / ${model.item.lastName}") } } class Person{ var firstName: String? = null var lastName: String? = null } class PersonModel : ItemViewModel<Person>() { val firstName = bind(Person::firstName) val lastName = bind(Person::lastName) } class MyTestApp: App(PersonEditor::class) fun main(args: Array<String>) { Application.launch(MyTestApp::class.java, *args) }
error- SEVERE: Uncaught error kotlin.TypeCastException: null cannot be cast to non-null type javafx.beans.property.Property<N>
which probably you have just fixed
e
@anidotnet TornadoFX never creates an item inside the ViewModel, you have to do that yourself.
a
ok. I think it would be better to point it out in the examples also.
e
Absolutely. I didn't realize this was unclear 🙂 Sorry about that, will look over the guide.