Data Bindings are a useful feature in Anvil that make it easy to keep your UI components in sync with your data. A Data Binding associates a property of a component with a single Python expression, saving you from repeating similar sorts of assignments all over your code. In this tutorial we're going to look at what Data Bindings are, how you can use them, and common gotchas.
We're going to look at data bindings by developing an example app for logging our favorite movie. Our tutorial will have three main parts:
- We'll build an app and use data bindings to display movie data
- Next, we'll extend the functionality to edit the data
- Then, we'll store the data to a Data Table
- Finally, we'll make some changes so that edits to our data persist when the app is re-run
The final app will look something like this:
All of this is done in Anvil's hosted environment through your web browser, all in Python.
If you're in a hurry, you can click here to clone the app and explore the finished version in Anvil.
If you're completely new to Anvil, you may want to try a 5-minute tutorial before beginning this tutorial.
Let's get started!
What is a Data Binding?
When you want to display the contents of a variable in a visual component you would normally use code like this:
self.label_1.text = self.title
If you wanted to update the Label component when self.title
changed, you would need to repeat that line of code in each place the variable changed. A data binding allows you to establish the relationship between the visual component and the variable once, eliminating all the assignment statements from your code.
Chapter 1: Displaying Our Favorite Movie in a Form
We'll create an app that shows information about your favorite movie. We'll use data bindings to display data from a dictionary into Anvil components.
Step 1: Create an App
Log in to Anvil and click 'Create a new app'. Choose the Material Design 3 theme.
That gives us an app with a Form named Form1
.
Step 2: Add components to your form
We'll build our UI by dragging-and-dropping components from the Toolbox into the page.
We want to display the movie name, the year it was released, the name of the director, and a summary of the movie's plot.
Add eight Labels, four for the titles of what we're displaying and four for the values to display. We'll also add a Label into the title slot.
The final form should look something like this:
Step 3: Initialize data sources
Every Anvil component and Form has an item
property, meant specifically for storing data of any kind. Our data source will be a dictionary that we'll store in the item
property of our Form.
Let's go to the Editor's Code view and create this dictionary in the __init__
function of the form:
class Form1(Form1Template):
def __init__(self, **properties):
self.item = {
'movie_name': 'Back to the Future',
'year': 1985,
'director': 'Robert Zemeckis',
'summary': 'Doc Brown invents a nuclear powered time machine, which Marty McFly '
'then uses to nearly erase himself from existence.'
}
# Set Form properties and Data Bindings.
self.init_components(**properties)
Note that the dictionary is created before the call to self.init_components
. This is because the data bindings are processed in that call, so our data source (the self.item
dictionary) must be set up before that.
Step 4: Set up data bindings
Now we need to tell Anvil what data to associate with which components (e.g. the data bindings). We'll bind the data from our self.item
dictionary to the text
property of the Labels we just added.
To set up a data binding, click on the component that you want to have Anvil automatically set. I'll start with the label that will hold the name of the movie.
You can find the text
property at the top of the Properties Panel. We could type something in there but that would then always show that text. Instead, click the link icon to the right of the text
property. This will cause the display to change and the link icon to turn blue, indicating that the data binding is active.
Set this data binding to the movie_name
key of our dictionary:
We'll now do the same for the other labels, using self.item['year']
for the year the movie was released, self.item['director']
for the director's name, and self.item['summary']
for the plot summary.
After those are set up, we can double check that the data bindings are correct by looking at the Form's Data Binding summary. You can find this by selecting your Form and scrolling to the bottom of the Properties Panel.
Step 5: Run the app
Your data bindings are all set! We've told the components which variables to pull data from to populate their text properties.
Run the app now and see the information about your movie displayed in the Form. We didn't need to use any code to set the text
properties of any of the Labels, they were all set through the data bindings we set up in the Labels' text
properties.
Chapter 2: Edit Movie Data
We'll next allow the user to edit the movie data. For this, we'll use a feature of data bindings called writeback. You can toggle data binding writeback for user-editable components. This means that when a user changes a property that has a data binding, the original variable will be updated with the new value.
Step 1: Create your edit form
Create a second Form in your app by clicking the three dots to the right of Client Code and choose Add Form. Select Blank Panel and rename it to MovieEdit
.
Start by dropping a ColumnPanel into the page. Setting up the Form is very similar to what we did with our previous Form, but we'll use TextBoxes instead of Labels, since we want the user to be able to edit the fields. Use a TextArea for the plot summary since that will usually be longer than a single line. This is how it should look:
Step 2: Set up data bindings
Setting up data bindings is much the same as before. Here's the one for text_box_1
, binding its text property to self.item['movie_name']
:
In this case, though, notice that there's a W at the right end of the data binding. That W
stands for writeback and will show on any property that's user-editable. Writeback defaults to inactive, but if you click the W you can activate it.
When writeback is active and the user edits the value in the Text Box, Anvil will update the original data binding source with those changes. It's the equivalent of you executing self.item['movie_name'] = self.text_box_1.text
when the TextBox value changes.
Since our purpose is to allow the user to edit the movie information, we want to enable writeback for all of these editable components. Set up the rest, and then check them in the Form's Data Binding summary. They should look like this:
Note that we didn't write any code in the MovieEdit
Form. That's the value of data bindings, to automate the boilerplate code of assigning values to visual components and then getting the values out when the user makes changes.
Step 3: Trigger your edit form
We want to access our MovieEdit
Form from our main Form. We'll do this by displaying the MovieEdit
form in an alert every time a user clicks a button.
Add a Button on Form1
. Set its text to 'Edit movie information' and change its name to edit_button.
We want some code to run when this Button is clicked. We'll use events for this. Select the Button and click on 'on click
event' in the Object Palette. This will open the Editor's 'Split' view and automatically create an edit_button_click
method associated with the button.
We'll write the code that will run when we click edit_button
inside this method. First, import the MovieEdit
Form at the top of the Form:
from ..MovieEdit import MovieEdit
And then in the edit_button_click
function itself create an instance of the MovieEdit
Form:
def edit_button_click(self, **event_args):
editing_form = MovieEdit(item=self.item)
When we create the instance of the MovieEdit
Form, we're going to pass our dictionary of movie information as its item
property. That will make it available in MovieEdit
as self.item
.
Now we're going to display the Form in an alert. We'll set the content of the alert to be editing_form
and the large
property to be True
:
def edit_button_click(self, **event_args):
editing_form = MovieEdit(item=self.item)
alert(content=editing_form, large=True)
Run the app and click the editing button to see the movie information in the text boxes and text area. If any of the information doesn't show up in a component, double check the data bindings.
Step 4: Read the changed values
The next step is to get the changed values back into the main Form. We are actually almost there since MovieEdit
's writeback data bindings are already changing Form1
's self.item
dictionary. But Form1
doesn't know that those values have changed, and so doesn't know to rerun the data bindings to update the components.
We need to tell Form1 to update its data bindings after the alert returns. We'll do this with self.refresh_data_bindings()
:
def edit_click(self, **event_args):
editing_form = MovieEdit(item=self.item)
alert(content=editing_form, large=True)
self.refresh_data_bindings()
Run the app now and edit the movie information to see it changed in Form1
!
We can now edit our app's movie data!
Chapter 3: Storing data in Data Tables
We can now edit our favorite movie data, but because it's stored in a dictionary, it won't persist when the app is re-run. Because of this, you'll usually want to store data in a database when using data bindings.
In this chapter, we'll change our app to store the movie data in a Data Table so that edits to the data will persist between runs of the app. Data Tables are an out-of-the-box option for data storage in Anvil, and they're backed by PostgreSQL.
Step 1: Create a Data Table
In the Sidebar Menu, choose Data to open up your app's Data Tables.
Next, click '+ Add Table' to add a new Data Table to the app. Name the table 'movies'.
With the new table selected, you'll see the interface for editing the Data Table. We'll set up our Data Table with 4 columns. We need to add the following columns:
-
movie_name
(a text column) -
year
(a number column) -
director
(a text column) -
summary
(a text column).
We can now copy the data from the self.item
dictionary and add it to our table.
Notice that we've used the same names for the columns as we used for the self.item
keys, so that we do not need to change our data bindings.
Step 2: Configure the Data Table
Data tables live on the server, which is a secure and trusted environment. Client Forms and client code run in the user's web browser, which the user has access to. Sufficiently motivated and knowledgeable users can modify the client code of any web app to make it do anything they'd like it to do, including removing any security checks you might put into client code.
By default Anvil Data Tables are only accessible from server code because this is the most secure option. You can see that in the Data Table interface:
You have two other options for client code. You can give client code read access (client code can search this table) or full access (client code can search, edit, and delete). Read access is appropriate for tables where there are no access restrictions needed on who can see data in the table. Full access is almost never appropriate for client code since it allows sufficiently motivated users to bypass all your security restrictions and all your validation code.
We're going to use read access for our table since we only have the one row that we want all the users to be able to read. After making that change the configuration should look like this:
Step 3: Read from the Data Table
Now we need to change Form1
to use the movies
Data Table as the source for self.item
instead of our dictionary.
We can access any Data Table with app_tables.<data-table-name>
. Since our table only has one row, we want to get the first row from the Data Table into self.item
in the __init__
function for Form1
.
class Form1(Form1Template):
def __init__(self, **properties):
self.item = app_tables.movies.search()[0]
# Set Form properties and Data Bindings.
self.init_components(**properties)
What app_tables.movies.search()
returns looks a lot like a Python list, so we can use standard list indexing to pull the first item out of the list. What's stored in the list are data table rows, which look enough like Python dictionaries to be used in self.item
with data bindings.
You should now be able to run the app and see the information from the data table showing on the main page of your app. Editing doesn't yet work, but that's our next step to fix.
Chapter 4: Store edits to the Data Table
Because we are not allowing the client to write to our Data Table, we cannot use writeback as we have been.
Step 1: Update the Data Table
Instead, we'll use a server function to update the Data Table.
Anvil's Server Modules are a full server-side Python environment. Unlike client code, Server Modules cannot be edited or seen by the user, so we can trust them to do what we tell them. This is why we'll use a Server Module to write to the Data Table.
For more information about the difference between client and server code, check out this explainer.
Let's create a Server Module. In the App Browser, click the blue '+ Add Server Module' button underneath 'Server Code'. This will open up a code editor.
Our server function will be taking in a dictionary from the client and updating the Data Table with its contents. We'll decorate it with @anvil.server.callable
so that we can access it from client code. Remember that we're still dealing with just the single row in the Data Table.
@anvil.server.callable
def update_movie(movie_data):
row = app_tables.movies.search()[0]
row['director'] = movie_data['director']
row['movie_name'] = movie_data['movie_name']
row['summary'] = movie_data['summary']
row['year'] = movie_data['year']
Server functions also give us a place to put any data validation we need. In the case of the movie data, we probably don't want to allow any of the fields to be empty.
@anvil.server.callable
def update_movie(movie_data):
if movie_data['director'] and movie_data['movie_name'] and movie_data['summary'] and movie_data['year']:
row = app_tables.movies.search()[0]
row['director'] = movie_data['director']
row['movie_name'] = movie_data['movie_name']
row['summary'] = movie_data['summary']
row['year'] = movie_data['year']
The exact validation you do depends on your app and the server function, but it's the spot to do last minute validation before you write changes to the Data Table.
Step 2: Make a copy of the Data Table row
Switch to the Code view for Form1
and look at the edit_click
function.
We can no longer pass self.item
directly to the MovieEdit
form, since self.item
is a read-only Data Table row and writeback will not work with it. What we can do instead is convert the item to a dictionary, effectively making a copy of it. Modify the edit_click
function to do so.
def edit_click(self, **event_args):
item = dict(self.item)
editing_form = MovieEdit(item=item)
alert(content=editing_form, large=True)
Writeback data bindings will work fine on the dictionary, since it's no longer a Data Table row, but a dictionary with copies of the values that were in the Data Table row.
Step 3: Update the Data Table row
After the alert is closed, we must call the server function to update the Data Table row and pass it the item
dictionary. We do that by using anvil.server.call
to call our update_movie
server function.
def edit_click(self, **event_args):
item = dict(self.item)
editing_form = MovieEdit(item=item)
alert(content=editing_form, large=True)
anvil.server.call('update_movie', item)
Step 4: Update self.item
Now that the server function has been called, we must update the main Form's data. We'll reset self.item
by getting the now updated row from our Data Table, then we'll use self.refresh_data_bindings()
to trigger the Form to update the text property of its components:
def edit_click(self, **event_args):
item = dict(self.item)
editing_form = MovieEdit(item=item)
alert(content=editing_form, large=True)
anvil.server.call('update_movie', item)
self.item = app_tables.movies.search()[0]
self.refresh_data_bindings()
Now, edits to the app will persist when you re-run the app. All users will see the edits that any users make.
Run the app, edit the movie information, and then stop the app and verify the data in the Data Tables has changed.
And that's it! We have built a simple app to showcase data for your favorite movie. From here, you could extend your app's functionality, for example by allowing users to have their own separate favorite movies.
What's Next?
Head to the Anvil Learning Centre for more guided tutorials, or head to our examples page to see the variety of apps you can build with Anvil.
Top comments (0)