Can you set a range on a number TextBox

I have a text box with the type=number Is there a way to set a limit on the number that can be entered in that box.
It is a dynamically created box so I wont have access to the properties tab.

I want to set it, so no one can enter any number greater than 100.

Hello,

You could use the ā€œchangeā€ event on the text box (or any other event that you might prefer). That is, when change is detected, a function is called. That function can check if a number is greater than 100 and respond accordingly (pop up an alert, set the number to 100, etc).

Please have a look at the event documentation/tutorials as they are key parts of what makes Anvil so powerful.

Check out the events and docs here but the tutorials will make it much clearer:

3 Likes

@campopianoa thank you, I have now done this and set an alert if the entered number is outside of my range.

The problem is, the type of event I set.
If I set it up as a change, this was a non starter, as it adds my result the second you press the first key
If I set it up as a lost_focus, I get a TypeError: '>' not supported between instances of 'NoneType' and 'int'
It works as a pressed_enter event

However, I find that mostly when I am completing forms I press tab key, not enter and if you do that it will fill in the numbers and do nothing at all with the code unless you press enter after each entry.

Is there anyway to tell python that a tab key also acts as an enter key on a form?

Or do I need to re-structure my function?

Here are the functions:-


 #Create Traveller Boxes from Traveller Input  
  def text_box_travellers_pressed_enter(self, **event_args):
    self.label_traveller_ages.visible = True
    self.column_panel_all_travellers.visible 
    global trav
    trav = int(self.text_box_travellers.text)
    trav = trav + 1
    global tb
    tb = self.tb = {}
    self.cp.clear()
    global i
    i = 1
    for i in range(1, trav):
      self.tb[i] = TextBox(type="number",foreground="#000",background="#fff",placeholder=f"traveller{i}")
      self.tb[i].role = "input"
      self.tb[i].tag.name = i
      self.cp.add_component(self.tb[i], row=1, col_xs=1, width_xs=1)
      self.tb[i].set_event_handler('pressed_enter', self.add_to_ages)
      pass
  
  #Adds the result of self.tb[i] to a tuple self.ages, if the result is correct
  def add_to_ages (self, sender,**event_args):
    print(sender.text)
    if not sender.text:
      pass
    if sender.text >100:
      alert("Age must be less than 100")
    else:  
        self.ages.append(sender.text)
        global ages
        ages = tuple(self.ages)
        print(ages)
  pass

I think the only problem here is that you need to check first if your textbox is empty. EVERYTIME you use tab to leave the text box you trigger the lost_focus event, even when the textbox is empty. But when it is empty, you can’t evaluate a comparison between the empty value (None) and an int value.

So you need to:

def text_box_lost_focus(self, **event_args):
  if self.text_box.text is not None:
    # Code for the validation

If your text box is empty (None) then there’s nothing to validate. You could also do a different thing if it’s empty, like tell the user that this text box shouldn’t be empty or things like that.

2 Likes

Rather than checking first, there’s an unwritten Python principle of ā€œit’s easier to ask for forgiveness than permissionā€. There’s a good description of that here:

In this case that means:

def text_box_lost_focus(self, **event_args):
    < do some preliminary stuff >

    try:
        < do stuff with the text >
    except TypeError:
        < handle the text box being empty >
    
    < do whatever else needs doing >
2 Likes

Thank you both, I will look at all of this before proceeding.

Edit: After thinking in general about what we are asking. we’ve decided to change these fields to date select, as we are asking for peoples ages… its probably an easier answer to get a date of birth, then we calculate the age, than for a user to have to work it out.

Now I’ve changed the type to datepicker, I no longer get the option of inbuilt pressed enter or lost_focus events so I need to create a custom event.

On the Anvil Docs page Anvil Docs | Custom Components
It looks like there is a place to create these?


Where do I find this in the new beta menu? I need this because as soon as you click on the day, it sets the whole date and triggers the event.

What you are saying are custom events for custom components, which is not your case, where you want to create a custom event for a basic component.

The quick answer is, you cannot do what you asked, at least not easily. You will need to use the change event, which is the one available for DatePickers. This way you can redo everything as soon as the user changes the date.

Yeah, but I don’t see any problem here. When the user click on the day, it will select the complete date (day, month and year). It’s different than the change for text box that will trigger everytime the user types something and similar to it’s lost focus, since it will trigger when the user completes the data.

Thats what you would think, but as soon as you press the day, it considers the date done and triggers the change event. If you click on the drop down month/year then it doesnt consider the date done, but it does as soon as you put in the day. I think I am going to have to change the whole way its holding the data for that part of the form.

It actually makes the change event pretty useless on the datepicker, at least if you want to do what I want, which is to add it to a tuple.

Oh well, back to the drawing board.

That’s exactly what is supposed to do, in my understanding of the component. The last thing you should click is the day. If you want to set to a date in another month/year, you should first change to the specific month in the specific year and THEN choose the day. When you click on a day you are choosing a date for that day. It’s pretty ā€œfinalā€.

I think it’s pretty spot on for all my usages, but as you said, maybe it doesn’t work for you. Care to explain more on what you want to do with the data and how you want to use it?

So, I have a travel quotation form, the first question it asks is, how many travellers, and depending on this, it creates ā€˜x’ number of datepicker boxes for the traveller date of births.

I originally had a textbox which required ages, rather than DOB and did get that working with some validation, but then we discussed the fact that while the user may know their family date of birth, they’d have to calculate the age. Therefore a datepicker saves all that, as we need that info anyway later on in the quote, so we could do the calculation.

But, because the calculation isnt done yet, we just want to store the date in a tuple so we can pull it out later for the rating (each person is rated on their age)

But, what happens is that each time a person changes the date if they did it wrong, it just adds a new date to the tuple. I know its probably just the fact that Im new to python so dont know how to correct those mistakes, and as I created those boxes with code self.tb[i] I have no idea how to correct those changes in the tuple.

Here-in lies my problem, and it would have been much easier if I had a pressed_enter event like I did on the textbox.

I might well have misunderstood something here, so my apologies if that’s the case, but instead of storing the date as it is selected, would you not do better to step through each of the date pickers later in the form’s flow, when you need them for whatever calculations you are performing?

If so, you would put your N number of date pickers in, say, a column_panel and do something like this (pseudo-ish code) :

# Assumes your column panel containing the date pickers is called cp_dp ...
for c in cp_dp.get_components():
    if type(c) is DatePicker:
        # Process the date in this date picker.

This way you do not need to store a separate list of dates entered, and the process will always have the currently selected date to work with.

Am I making any sense, or have I completely misunderstood?

1 Like

@david.wylie you are 100% correct, my problem is I dont know how to reference the component.

I see what you are saying:
I am trying to find the name of it, so where I have fixed components, I have named them things like text_box_age and these ones are self.tb[ i ]
What you are saying is I dont need to know that because I only need to know the type. So how would I reference the entered date?

In my example, the variable c is now a reference to the DatePicker component, so you can access all its methods & properties.

Another way to achieve the same thing would be to use that form property self.tb to store the actual references to the objects. So for example, you would create the datepickers like this :

self.tb = [] # always blank this before creating the date pickers.
for i in range(number_of_datepickers_to_create):
    dp = DatePicker(...) # Creates a new instance of a date picker.
    self.tb.append(dp) # adds the reference to that instance to a form property.

Now you can do :

for c in self.tb:
    # c = a date picker object.

Both methods are similar. One uses a variable array to store the object references, the other uses a container component.

edit - thinking about it, I assume you will need a way to know which date picker refers to which traveller?

edit 2 - I have obviously not tackled adding the component to the form or any other validation you might need, I’ve just commented on the immediate issue you posted.

1 Like

I think we’ll be reverting to a TextBox :slight_smile: as I had got that working! But thank you for the info on getting every type in a component, I think I will find that really useful!

1 Like