Creating an Event on a coded element?

I have created a form, and one element creates textboxes based on the number of travellers, it then asks for the ages.

I want it so that if you press enter on the text element, the entered text is added to a list.

I’ve gone through the docs and tried to create that event here, so if 4 text boxes are created, I want 4 entries in my list. I know its not as simple as just adding to the list, because they may change the result, but at least I want to get this bit working.

Also, what I need to append is an int, not a str, because the result will need to be used in an sql select for results containing those ages.

Here is my code

  def text_box_travellers_pressed_enter(self, **event_args):
    global ages
    ages=[]
    global trav
    trav = int(self.text_box_travellers.text)
    trav = trav + 1
    global tb
    tb = self.tb = {}
    self.cp.clear()
    for i in range(1, trav):
      if i<=4:
        row ='A'
      elif 5<= i<=8:
        row ='B'
      elif 9<= i<12:
        row ='C'
      else:
        row ='D'
      self.tb[i] = TextBox(bold= True,foreground="#000",background="#fff",placeholder=f"traveller{i}",)
      self.tb[i].role = "form-control"
      self.tb[i].tag.name = i
      self.cp.add_component(self.tb[i], row=row, col_xs=3, width_xs=2)
      global age1
      age1 = self.tb[i].text
      self.tb[i].add_event_handler('x-update', self.add_to_ages)
      self.tb[i].raise_event('x-update')  
    pass
  
  def add_to_ages (self, **event_args):
    ages.append(age1)
    print(ages)
  pass

What I am seeing at the moment on the console is this, so I am appending nothing as nothing has been entered into the boxes yet

['']

['', '']

['', '', '']

['', '', '', '']

This really needs to be better covered by the docs. You can set an event handler on your dynamically created text boxes for the pressed-enter event itself, e.g.:

self.tb[i].set_event_handler('pressed-enter', self.add_to_ages)

Then get rid of everything after your self.cp_add_component line in your text_box_travellers_pressed_enter function.

Inside that event handler function, you access the sender argument to get the text box that enter was pressed on:

  def add_to_ages (self, sender, **event_args):
    ages.append(sender.text)
    print(ages)

All of that’s from memory, so might not work as is, but gives you the general idea.

If your input type for your text box is set to Number, then the text attribute will get you back an int.

1 Like

Hi

I modified my function here and now I dont get the list box output on the console, the only time I get any console output is if I add this line

self.tb[i].raise_event('x-update')  
  def text_box_travellers_pressed_enter(self, **event_args):
    global ages
    ages=[]
    global trav
    trav = int(self.text_box_travellers.text)
    trav = trav + 1
    global tb
    tb = self.tb = {}
    self.cp.clear()
    for i in range(1, trav):
      if i<=4:
        row ='A'
      elif 5<= i<=8:
        row ='B'
      elif 9<= i<12:
        row ='C'
      else:
        row ='D'
      self.tb[i] = TextBox(type="number",bold= True,foreground="#000",background="#fff",placeholder=f"traveller{i}",)
      self.tb[i].role = "form-control"
      self.tb[i].tag.name = i
      self.cp.add_component(self.tb[i], row=row, col_xs=3, width_xs=2)
      self.tb[i].set_event_handler('x-pressed-enter', self.add_to_ages)
      pass
  
  def add_to_ages (self, sender,**event_args):
    ages.append(sender.text)
    print(ages)
  pass

You don’t want ‘x-pressed-enter’. The ‘x-’ prefix is for custom events. not the built in ones. You just want ‘pressed_enter’ (note the underscore instead of the dash…the name must match what you see in the events section of a text box).

2 Likes

brilliant, that works! Thank you. I was just getting confused as I put it as per their original example on the docs and when I removed the x,- it said I needed that.

So just to clarify, if I want a click event I just put ‘click’ and then the function.

Yeah, the docs don’t talk about setting the built-in events, only custom ones. They definitely need more work on that section.

Correct. You can use any built-in event name that you can see in the events section of the component’s properties in the IDE.

1 Like

so now I have my list of x number of items.

How do I connect that up to my sql statement on the server side?

This is my sql query

@anvil.server.callable
def ageQuery (currentage):
  conn = connect()
  with conn.cursor() as cur:
      cur.execute("SELECT AgeID, Age, AgePercent FROM age WHERE Age IN {currentage}".format(currentage=currentage))
  return cur.fetchall()

I have this click event on ths submit button, but if you look at Age, I want the list result in there - I’ve just put those numbers in for example. The list that contains these numbers is called ages.

    def button_1_click(self, **event_args):
        self.repeating_panel_1.items = anvil.server.call('locationQuery', self.text_box_location.text)
        self.repeating_panel_2.items = anvil.server.call('ageQuery', (30,40,50))
        self.repeating_panel_3.items = anvil.server.call('daysQuery', self.text_box_days.text)
        self.repeating_panel_4.items = anvil.server.call('baggageQuery', self.text_box_baggage.text)
        self.repeating_panel_5.items = anvil.server.call('singlearticleQuery', self.text_box_single_article.text)
        self.repeating_panel_6.items = anvil.server.call('cancellationQuery', self.text_box_cancellation.text)
  
  pass

I’ve been thinking that if python only stores the result of a list population within the function that created it, I should extend that function to include the server call. I am so stuck here though. Wow this is so much harder than typing it all into idle!

I checked and the value of x as set out below, does give me individual ages, if I type in 30,40,50 into the input boxes it populates the list
[30,40,50]

and then print(x) returns
30
40
50

Anyway, here is where I got so far:-

  def add_to_ages (self, sender,**event_args):
    ages.append(sender.text)
    print(ages)
    for x in ages:
      currentage = (x)
      cur.execute = anvil.server.call('ageQuery',x)
      for Age in cur.fetchall():
        ageis = Age
        print(ageis)
  pass

I then want the fetched data to populate the repeating panel

You’re using global variables when you really should be using instance variables. e.g., in your init have this:

self.ages = []

In your add_to_ages have this:

self.ages.append(sender.text)

In your server call have this:

anvil.server.call('ageQuery',self.ages)

1 Like

Thank you I will try that now.

Hi,

I am still doing something wrong, so can I just clarify, in my add_to ages, do I really only need to set the list self.ages, like this?

  def add_to_ages (self, sender,**event_args):
    self.ages.append(sender.text)
    print(self.ages)
  pass

The console output of that function is
[30, 40, 50, 60]

The in my click button server call I put

self.repeating_panel_2.items = anvil.server.call('ageQuery', self.ages)

Does that mean that the click action will run ageQuery on the output of add_to_ages? I can run the SQL query in phpmyadmin, and it works fine, so somewhere I am still going wrong, because I am getting this error
error

Line 31 is

Line 47 is

So, just so you can see what was done on the terminal copy of this code, this was what we did


# Get Age Data from Age Data Table - populates travellers.AgeID
def ageQuery( conn ):
    ages = []
    count = 1
    global totalagepremium
    totalagepremium = 0
    cur = conn.cursor()
    global trav
    trav = int(input("How many travellers  in total?: "))
    while count <= trav:
        age1 = input(f"What is traveller {count}'s age?: ")
        ages.append(age1)
        count = count + 1
        print(ages)

    for x in ages:   
        currentage = (x)
        cur.execute("SELECT AgeID, Age, AgePercent FROM age WHERE Age ={currentage}".format(currentage=currentage))
        for AgeID, Age, AgePercent in cur.fetchall():
            global ageis
            ageis = Age
            global agepercentis
            agepercentis = AgePercent
            global agetoadd
            agetoadd = baseprice * AgePercent
            totalagepremium = totalagepremium + agetoadd + baseprice

I think once I understand, how to split up these functions from the Idle Terminal, into

Server Side, Client Side, I will be able to translate other functions into Anvil.

I presume, anything where we have created variables, has to go on the client side, anything with an SQL Select has to go on Server side, but its how to connect those two up I am struggling with.

Here is how Ive translated the idle python so far. The first question is trav and the second question is age1

When I run that, I get this error message:

AnvilWrappedError: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '[5, 6]' at line 1")

Normal SQL for using in would be to surround the items with parentheses, not brackets. I’d say you’re having issues constructing a valid SQL statement. Why it works in Idle and not Anvil I don’t know, but it’s entirely possible you’re using different databases.

You should play around with printing out the SQL statement in your server function before you try to execute it, to see what it is you’re asking to be executed. Then work with that to get it to be the SQL you want.

1 Like

All of the SQL queries I have use that, but they all have a single result, I think somehow, what it happing is, because its a multiple result, the format isnt right.
Normally to get the result of an IN I would do this

SELECT * FROM age WHERE Age IN (30,40,50)

But I get this 1064 error if I run it like this - and this is what I think is happening.

SELECT * FROM age WHERE Age IN [30,40,50]

All my other inputs are individuals, and the curly brackets just enable the python result to be injected into the sql query. Thats how we got it to work on the Idle Terminal anyways.

I have asked Anvil Support for some more guidance here as I am conscious of how much time I am asking of you all on here.

Really, what I want is someone to break down that idle ageQuery function and say which bits need to go where in order to replicate the results. It was reaonably easy to do on all the drag and drop entries, since the keys are there in the properties and with some help on the layout that was fine, but since you cant see an advanced tab where all the code is behind it, I cant view it and see “oh right, thats where that goes” to copy it( I’m generally ok with that, since I re-vamped my other halfs website without much knowledge of php, using that process).

So, I have asked if they can provide a little more help, in the hope also that this will help others who like me, need some examples of how to translate terminal code into the UI, where you cant just drag and drop.

In the meantime, I am just starting the talkpython anvil training course… hopefully that will help.

The square brackets come when you convert a Python list into a string.

What you really should be doing is using a prepared statement rather than injecting the list directly into the SQL, that would allow the database to convert it into the right format.

A simpler option with your current code is to convert the list into a tuple (tuples convert to strings with parentheses). You can do that like any other data type conversion, e.g. tuple(mylist)

1 Like

so here is the add_to ages now, it does change the list to tuple, but…

  def add_to_ages (self, sender,**event_args):
    self.ages.append(sender.text)
    self.ages = tuple(self.ages)
    print(self.ages)

now I’m getting tuple has no attribute append GAH! Do I have to do another separate function to call the final result of self.ages then turn it into a tuple? If so how do I pull in the final list from add_to_ages

  def add_to_ages (self, sender,**event_args):
      self.ages.append(sender.text)
      print(self.ages)
      print(type(self.ages))
      
  def convert_add_to_ages (self, sender,**event_args):
      self.ages = tuple(self.ages)
      print(self.ages)
      print(type(self.ages))

I finally fixed this part of it @jshaffstall I’m giving lots of hearts to you for helping me, you have been a great help, more than I can really expect.

Here’s what I did:


  def add_to_ages (self, sender,**event_args):
      self.ages.append(sender.text)
      global ages
      ages = tuple(self.ages)
      print(ages)
      print(type(ages))

And added the .format to the main click event as follows:

self.repeating_panel_2.items = anvil.server.call('ageQuery', "{ages}".format(ages=ages))

but still would appreciate Anvil support showing an example of how I convert my ageQuery in its entirety from Python Idle to Python Anvil as I still dont know how to reference the keys instead of the MySql on that part

for AgeID, Age, AgePercent in cur.fetchall():
            global ageis
            ageis = Age
            global agepercentis
            agepercentis = AgePercent
            global agetoadd
            agetoadd = baseprice * AgePercent

You really wanted to do the conversion to tuple in your server function, not in the client. You could have maintained the list all the way up to the format call, and converted to a tuple at the last minute to get the format right for SQL.

Glad you got it working, though. I highly recommend the Python tutorials, Anvil does assume some basic level of Python knowledge of lists, tuples, dictionaries, list comprehensions, classes, etc. Getting that background will help a lot.

thanks, for the help. It is definitely appreciated. I did do the tuple in the server function first, but I did it on self.ages and it didnt like it, so I didnt even think about changing it in the server call, just went right back to the self.ages and converted it to ages without the full stop in the middle, then I knew I was sure I was getting the right result.

I found the Anvil Docs really useful for all the drag and drop elements, I do think though for students like my daughter (and complete non python writers, like me) a tutorial on re-structuring some Python Idle to fit in Anvil would be really helpful, since 100% of what she’s learned is in terminal.

1 Like