DEV Community

Cover image for Introduction to Data Bindings
Jay Shaffstall for Anvil

Posted on

Introduction to Data Bindings

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:

  1. We'll build an app and use data bindings to display movie data
  2. Next, we'll extend the functionality to edit the data
  3. Then, we'll store the data to a Data Table
  4. 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:

Final App

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
Enter fullscreen mode Exit fullscreen mode

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.

Create the App

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.

Creating the form

The final form should look something like this:

The Final Form

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)
Enter fullscreen mode Exit fullscreen mode

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.

Setup Data Binding

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.

Text Property

Set this data binding to the movie_name key of our dictionary:

Data Binding

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.

Checking Data Bindings

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.

Running the App

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.

Adding MovieEdit Form

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:

MovieEdit Form

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']:

Text Data Binding

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.

Writeback Active

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:

Data Bindings

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.

Edit Movie 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.

Creating the Click Event

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.

Testing the Alert

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()
Enter fullscreen mode Exit fullscreen mode

Run the app now and edit the movie information to see it changed in Form1!

Editing Movie Data

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.

Data Tables

Next, click '+ Add Table' to add a new Data Table to the app. Name the table 'movies'.

Add the Table

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.

Filled Data 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:

Data Table Security

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:

Data Table Client Reading

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)
Enter fullscreen mode Exit fullscreen mode

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.

Creating Server Module

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']
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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.

Data Tables Changing

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)