User input not valid; please Try Again Later …

50 views
Skip to first unread message

F Bek

unread,
Jan 22, 2021, 1:31:22 PMJan 22
to
Beginner's question
Dolphin Smalltalk 7.1.19

Continuing with my MVP, I have added a subclass or 2 to my model, with the corresponding sub-presenters and still managed to maintain a single View for all. the Model is directly connected to the presenter and is not buffered, which is pretty much what is needed.

I would like, however, to validate the input on a few cells, to prevent the user from keying in values outside a dynamic range, that do not make sense in a given context, even if not causing a system error.

I have tried 'myPresenter when: #valueChanged send: #myMethod to: self' from within #createSchematicWiring, which works, but after the fact, since the model is already updated with the new value and the old value is lost and cannot be restored if the validation fails.

Is there another, similar method, that could capture the input = before = it is committed to the model?

I am aware that I can 1)- let the model handle all validations, or, 2)- use a buffering Presenter providing access to both new value and old model value, and only commit after validation; but, 3)- I prefer to experiment with letting the View handle (part of) it upfront.

Eventually, I think a)- the Model should protect itself by not accepting any invalid input, and enforcing its last known state; It should also hold the validation rules and be able to communicate them to whoever asks; but it should have no other responsibilities. The Presenter should obtain those rules and actually carry out the validations, and, finally, the View should communicate to the user what he can or cannot do, in a preemptive way, by displaying some dynamic guiding messages, and rejecting non-complying input. How to do that, remains the question.

Thanks for reading and eventually answering.
FB
___________________________________________

klausk

unread,
Jan 23, 2021, 4:02:51 AMJan 23
to
>
> I would like, however, to validate the input on a few cells, to prevent the user from keying in values outside a dynamic range, that do not make sense in a given context, even if not causing a system error.
>
I let a TypeConverter do this for me if values have to be eg. currencies. Invalid values are suppressed. It then looks like "the computer hoesn't like my input". When editing a TextEdit inside the ViewComposer, look at the bottom of the attributes list...

Otherwise try ChoicePresenter. If values are in a range have a look at Spinner.

F Bek

unread,
Jan 23, 2021, 8:39:36 AMJan 23
to
Thank you Klausk,
Yes, TypeConvertier looks like the right place to capture user input and validate it. It was easy to intercept the conversion in #rightToLeft: and call a validation method, as in the following:

NumberToText>>rightToLeft: aString
    "Answers the result of converting aString to a Number."
    | newNumber |
    newNumber := Number fromString: aString.
   ^newNumber := self validateOrKeep: newNumber.

NumberToText>>validateOrKeep: newNumber   
"Validate the new number or keep the current <oldNumber> unchanged"
    | min max oldNumber |
    oldNumber := 'obtain it from TextEdit model'.
    min := 'calculate it'.
    max := 'calculate it'.
    ^(newNumber between: min and: max) ifTrue: [newNumber] ifFalse: [oldNumber]

In the above, <oldNumber>, <min> and <max> all of them depend on the current status of the model; <NumberToText> converter needs a way to communicate back with its <TextEdit> to obtain them.

How do I do that? 

Thanks for reading and eventually answering.
FB
___________________________________________


danie...@gmail.com

unread,
Jan 23, 2021, 6:10:20 PMJan 23
to
I notice you mention "buffering presenter", but—at least, the way I know how to do it—really it's not the Presenter that does the buffering, but a wrapper on top of the model. Check out <AspectBuffer>—you could take an approach where your model class *allows* itself to become invalid, but can answer whether it is valid and if not, why not. The presenter can display messages accordingly, and only flush the updated values to the "subject" of the buffer (the original model) if they are valid—thus, you always have a valid model object on hand if other logic needs to use it.

I generally wouldn't use a TypeConverter for complex semantic validation—only for what essentially amounts to text formatting, i.e. restricting to numbers. It can be very disconcerting to a user to have some keystrokes appear to be simply ignored, if the reasoning is not immediately and blatantly obvious. Much better to allow them through, display what was typed and explain exactly why it is not valid.

F Bek

unread,
Jan 25, 2021, 8:50:16 AMJan 25
to
Thanks for the feedback, yes, "buffering presenter" was misleading and your description of an <AspectBuffer> wrapper on top of the model is correct. But I don't really need to buffer the whole model though, as most of the time working directly with the model is fine. And I have looked at <Dialog> #model: which readily works with a buffered model, for some idea about how this is done. I could therefore try and learn something else maybe.

So I think I will investigate a way to buffer only the 2 values of interest, directly in the Presenter or through an AspectBuffer - and use the buffer as fallback or to calculate validations -  and see how it goes. 

On another note, and in order to ensure the model is reusable, I think I should limit the model exception/error handling to those cases that would break the calculations only, while basic validation of input is handled closer to the user, and depends on the purpose of the application - a direct Presenter-View connected to the model (as in this case) may have different validation requirements compared to when the model is plugged into a larger application, as a specialized module (as in a future case). I do not feel the need to burden the model itself with lots of validation and exception coding addressed to an eventual user - am I on the right track here? I don't know.

I now realize also that <TypeConverter> may not be the best place to do the validation, and anyway, due to encapsulation of data, it does not seem to have access to the model - unless there IS a way? 

Going backward one step, <TextEdit> would be the next candidate, but I would hate to mess up with <TextEdit>'s  core methods for #apply; #updateModel; and, super #updateModel (where the call for - and the return of - the TypeConverter. takes place).

That brings me back to the Presenter itself, and if I manage to buffer those 2 values, I can let the presenter do all sorts of validations, and if the model becomes temporarily invalid, then be it, as you said,  until the user keys in another value...

Your further comments and feedback would be appreciated.
FB
___________________________________________

F Bek

unread,
Feb 17, 2021, 3:55:47 PMFeb 17
to
On second thought, validation turned out to be a bit easier than anticipated. Again, the starting point is #createSchematicWiring, with one method (#focusGained) to intercept focus, one method (#when:send:to:) to process the change, and one instance variable <buffer> to hold the old value for a while, as in:

  createSchematicWiring       "capture entering a new Total Height or a new Total Weight"
      super createSchematicWiring.
      totalWidthPresenter when: #focusGained send: #setBuffer: to: self with: totalWidthPresenter .
      totalHeightPresenter when: #focusGained send: #setBuffer: to: self with: totalHeightPresenter . 
      totalHeightPresenter when: #valueChanged send: #totalHeightChanged to: self .
      totalWidthPresenter when: #valueChanged send: #totalWidthChanged to: self .


  setBuffer: bufferedObject
      "save the <bufferedObject> argument value, before it changes, for later processing or validation"      buffer := bufferedObject value . 

  totalWidthChanged      "Validate the new value"
      (totalWidthPresenter value >= widthPresenter value) ifTrue: [ rectangle2WidthPresenter value: totalWidthPresenter value ]
                     ifFalse: [ totalWidthPresenter value: buffer ;  view refreshContents .  MessageBox errorMsg: '''Total Width'' value out of bounds'  ]

  #totalHeightChanged - is treated in a similar way.
  
In #totalWidthChanged, the input value can be processed against any set of rules needed, and if this fails, the old value is reinstated from the saved value, the presenter is refreshed and a message box is displayed to inform the user accordingly.

The next step is to display a text box next to the presenter with the focus, guiding the user by displaying the range of acceptable input values...
___________________________________________
Reply all
Reply to author
Forward
0 new messages