Yet another validator and formatter

Long time ago I created my own version of an input Validator and formatter, inspired by @meredydd’s post.

Over time I have improved it and recently added Anvil Extra’s popovers as the optional way to show validation errors.

I am posting it here because I have used it for a long time in many apps and I think it’s mature enough for show time.

Here is the clone link for the demo app and the Validator dependency:
https://anvil.works/build#clone:6YJRNIYC6XTKYJ2E=7U3N2HLOGXAH3LIESKIBA34X

Here is the demo: https://stefano-validator-demo.anvil.app

When you start the demo you have two links on the navigation bar. One shows the validation in action using labels, the other using popovers.

A label shows the code used for the validation in front of each input component.

The code inside the demo is self-explanatory and shows how the validator is used.

Here is a quick preview:

from Validator.validator import Validator

  def __init__(self, **properties):
    self.init_components(**properties)

    self.validator = Validator()
    self.validator.between(component=self.text_box_1,
                           events=['lost_focus', 'change'],
                           min_value=5,
                           max_value=10,
                           include_min=True,
                           include_max=False)
    self.validator.number(component=self.text_box_8,
                          events=['lost_focus', 'change'],
                          format='float 07.3f')
    self.validator.with_function(component=self.text_box_13,
                                 events=['lost_focus', 'change'],
                                 validating_function=lambda tb: tb.text >= 'c',
                                 message='Must be greater than "c"')

  def check_all_click(self, **event_args):
    print(self.validator.are_all_valid())
16 Likes

This is awesome!

Abstracts away so much of the tedium that normally comes with UI work :slight_smile:

2 Likes

This is fantastic thank you @stefano.menci!!

Very easy to use!

3 Likes

I have fixed two little problems in the Validator (thanks @rickhurlbatt):

  1. required works also with text boxes with format set to Number
  2. I replaced set_event_handler with add_event_handler, so events defined in the IDE work together with events defined in the Validator
2 Likes

Hi there. Thank you for this library. As an Anvil newbie, it’s very nice to have as a guide.

I’m curious how you do server side validation when using this? Do you just rewrite code on the server side to handle it and duplicate your rules?

It’d be wonderful to write your validation “rules” in one place, and then the validation behavior change based on where it’s being called. So, for example, on the client side you might display an error message in a label or popup, while on the server you might just return a message indicating what is wrong for logging.

I’d be very interested to know if you’ve come with a solution which covers both sides.

Thanks!

There is no server side validation.

I see how writing validation rules in one place could help, but I never had an app passing data to the server directly from the input components.

I get the input from the forms, pass it to an object crunch it, digest it, transform it, and eventually serialize the object and send it to the server.

The server will validate the data coming from the form, but at that point the data has been transformed, and defining the validation during the form input makes no sense, even for the fields that pass without transformation.

2 Likes

Server-side code can call simple client-side modules. (This happens, for example, when you define a Portable Object class.)

Thus, you could put validation rules in a client-side module, and call it from both client and server. Code duplication is not required.

1 Like

This works in simple cases, but sometimes the validation of the value of each interface component is different form the validation of the whole user input, so on the server side I never validate the user input, I only validate the object generated with it. There are simple cases where the input validation is identical to the business logic, but for consistency, I like to always manage the business logic as “smart” business logic and the user input as “stupid” user input.

For example, if a form asks for width and height of a box, I usually set something like:

self.validator.greater_than(component=self.width, min_value=10)
self.validator.greater_than(component=self.height, min_value=10)

At this point the user can type any value greater than 10 for each input. This validation gives a quick feedback in case the user does something obviously wrong.

But then, every time the user changes a value, the value is passed to the object containing the business logic and the object will determine if that combination is valid or not.

For example a 20x20 box may be valid for candies, but not for cookies, or it may be valid for a type of shipment, but not for another, or the validation may be in the box calculated weight. If that is the case the app will decide what to do and maybe treat it as a generic invalid input or give some more constructive feedback.

This type of validation can’t really be applied to one input component, because it is not the width that is wrong, or the height or the type of shipment. It’s the combination of them all. Validating all the inputs at once could generate a Christmas tree of validation notifications that I really don’t like.

When it is possible to blame one specific component, I can do this:

self.validator.with_function(component=self.width, validating_function=object.validate_width)

But even in this case I don’t need a Validator class to tell me that I need to use the validation methods for the validation. It’s the other way: I’m giving the Validator the validation code written only once. And the validation code is where it belongs: inside the business logic manager, not inside the user interface.

When the form is ready to send data to the server, the object is serialized, sent to the server, deserialized, validated, etc.

The server side validation doesn’t care about the width being >= 10, it only cares that the box parameters are valid according to the business logic.

Summary: the client validates the user input, while the server validates a deserialized object.

1 Like

I recently started using the Validator for a side project. I also started trying to incorporate more Anvil Extras components, such as the autocomplete text box. I quickly discovered that the required validation didn’t work for that component because the Validator was checking specifically for TextBox and TextArea components using:

if type(component) in (TextBox, TextArea):

This prevents it from being used for other components that act like a text box by exposing a text property. While I could use a validation with function to get around it, I like the convenience of the required validation.

I was able to get the behavior I wanted in my copy of Validator by changing the above line to:

if hasattr(component, 'text'):

DatePicker and DropDowns don’t have a text property, so it shouldn’t affect them, but that opens it up to a wide variety of custom components that expose text.

Just FYI, for anyone who might run into similar issues.

6 Likes

Thank you.
I have added your change to the clone link app.

I do use the Validator with custom components that expose the text property, but I had never used the required validation with them.

3 Likes

@stefano.menci Very useful!
Would you consider packaging it as a third party dependency (with token)? Also, maybe, adjust the name to avoid confusion with Anvil’s Validator? OTOH that one is not very official and/or supported. So maybe just claim the name :wink:

Oh BTW. The email validation is incomplete. A valid email address like test@condé.com will fail, probably due to the accent.