How to correctly identify a dynamically created textbox

Following on from this topic Can you set a range on a number TextBox - #15 by admin1
I’ve gone back to a TextBox on this part of the quotation, but I still dont like the way its working, I think it has too much potential to mess up the quotation, as - at the moment - if the user went back and changed any of the ages, it would just add another entry to the tuple.

I’d prefer like @david.wylie said to do the calculation later on, when the user clicks submit. So I created this code,

    for c in self.column_panel_all_travellers.get_components():
      if type(c) is TextBox:  
        self.tb[i] = self.travage
      if self.travage >100:
        alert("Age must be less than 100")
      else:  
        self.ages.append(self.travage)
        global ages
        ages = tuple(self.ages)
        print(ages)

It is looping through the TextBoxes, though, in this quote I had 4 TextBoxes, so it did give me 4 results, but all zeros

(0,)

(0, 0)

(0, 0, 0)

(0, 0, 0, 0)

Here is the code that creates the boxes, as you can see I tried to put the tag.name as self.travage, but that hasnt worked.

The only thing that transferred the correct field was sender.text, but you have to click enter each time on a field and then it sets it before the user has clicked submit that they are happy with what they entered. I dont want to do that, I want each box to be treated individually then the calculation done at the end.


 #Create Traveller Boxes from Traveller Input  
  def text_box_travellers_pressed_enter(self, **event_args):
    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 = self.travage
      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)

Can anyone tell me how I should correctly reference the output of self.tb[ i ]

What about something like this?

 ages = []
 for c in self.column_panel_all_travellers.get_components():
      if type(c) is TextBox:  
        ages.append (c.text)

And then you can do whatever error checking you want to do on the individual ages by looping through the ages list, send the age list to the server, etc.

1 Like

ok, thanks will give that a go.

Also, for what it’s worth, I would eliminate the global variable feature from your Python vocabulary when working with Anvil forms. Global variables come in handy when caching data client-side in Anvil, but even then the use of global is limited to the client side module and doesn’t show up in forms.

3 Likes

Thanks I have done that and it works perfectly thanks.

In regards to the error handling, so far I used the talk python course to put in some basic error handling for missing data

its in a function built into the submit button.

its in the format:

    if not self.drop_down_baggage.selected_value:
      return "Baggage Value is Required"
try:
self.baggage = (self.drop_down_baggage.selected_value)

Since I have x number of fields and dont have any ids for them, how would I work this into that function?

for c in self.column_panel_all_travellers.get_components():
        if type(c) is TextBox:
          c= (c.text)
          if c.text > 100:
            return "Age Must be Less than 100"

I’m not sure what you would put in the try: Would it be a function call which handles the query, and if so how would you format that in the try ???

I’m not sure what that try block is doing, so I can’t really say. For purely validation purposes it doesn’t seem to be doing anything, unless there’s more code in there than what you’ve shown.

yes there is more code, I was just conscious that all of my posts are rather long :slight_smile:

Here is the whole thing and its linked to the submit button. I just added that code in and nothing happened, even when I put an age higher than 100

#Error handling
  def sync_data(self):
    if not self.drop_down_baggage.selected_value:
      return "Baggage Value is Required"
    if not self.drop_down_days.selected_value:
      return "Number of Days for your Trip is Required"
    if not self.autocomplete_location.text:
      return "Location is Required"
    if not self.drop_down_single_article.selected_value:
      return "Single Article Value is Required"
    if not self.drop_down_cancellation.selected_value:
      return "Cancellation Value is Required"
    return None
  
    try:
        self.location = (self.autocomplete_location.text)
        self.days = (self.drop_down_days.selected_value)
        self.baggage = (self.drop_down_baggage.selected_value)
        self.article = (self.drop_down_single_article.selected_value)
        self.cancellation = (self.drop_down_cancellation.selected_value)
    except TypeError as te:
        return "Invalid Format: Could not Convert data"
    except Exception as x:
      return "Unkown Error {}".format(x)
    
    for c in self.column_panel_all_travellers.get_components():
        if type(c) is TextBox:
          c= (c.text)
          if c.text > 100:
            return "Age Must be Less than 100"
          else:
            return None
          
    return None

I don’t see any way that those instructions in the try block could ever throw an exception. There’s no data conversion being done in them. Especially for the drop downs, the user can only select from the options you gave them.

As written, it looks like you could eliminate the entire try/except block and get the same functionality out of your validation function.

You do have a bug, though:

    for c in self.column_panel_all_travellers.get_components():
        if type(c) is TextBox:
          c= (c.text)
          if c.text > 100:
            return "Age Must be Less than 100"
          else:
            return None

You do not want an else returning None inside your for loop. That will return None on the first valid age, and then will not check any of the remaining ages.

I did basically just copy the exception list from the course I watched and since then I have changed a lot of the boxes to drop downs and auto complete, to really avoid errors, so you are right they wont throw exceptions any more.

The ages one is really my only issue now, but there may be other things, because once I have finished this bit, the user will need to enter dates of births for travellers, so I will need to check that the ages correctly represent the full date of birth, but for now I just want to make sure someone hasnt entered anything over 100.

I’ll correct that error now and remove all the unecessary checks.

EDIT

So, on my revised error check I get

AttributeError: 'int' object has no attribute 'text'

I think its because I am asking for c.text which is an int value not text. If I print c.text though, like I do here

    self.ages = []
    for c in self.column_panel_all_travellers.get_components():
      if type(c) is TextBox:  
        self.ages.append (c.text)
        print(self.ages)
        print(c.text)

c.text does print out all the ages., I tried the query int(c.text) but that made no difference, I tried changing it to c.number and got this AttributeError: 'TextBox' object has no attribute 'number'

Well, I fixed it by creating a dummy list, then running the check over it.

def sync_data(self):
testages = []
agelimit = 100
for c in self.column_panel_all_travellers.get_components():
if type(c) is TextBox:
testages.append (c.text)
for x in testages:
if x > agelimit:
return “Age Must be Less than 100”

Sorry, didn’t see the second bug sitting there. You set c equal to c.text, and then immediately do c.text again. For that second c.text c is an int. You just wanted:

          c= (c.text)
          if c > 100:
1 Like

No worries and thank you! You’ve been an amazing help.