Let's Build A Real Estate Listing, using HubDB

In this tutorial, I’ll show you how to create a Real Estate Listing on HubSpot’s CMS, using HubDB.

You’ll need:

  • A HubSpot account with the CMS Add-On
    • Portals with Website Starter or without the CMS Add-On do not support HubDB
    • You can check if your portal has the CMS Add-On by signing in here.
  • Approximately 3 hours
  • Some prior knowledge of HubSpot's CMS, HTML and CSS will be needed to customize your page


Build your Listing Database

Create a new table in HubDB and add your columns. In this example, we'll build a database using a few different columns that will generate our Listing's. This example uses the following columns and types:

  • listing_image (Image)
  • listing_price (Text)
  • listing_address (Text)
  • type (Select)
  • listing_bedrooms (number)
  • listing_baths (number)
  • listing_sq_ft (number)
  • listing_acres (number)
  • listing_url (url) -- If you want to link to a full Detail view

You can build your own database of listings or you can import some testing data for walking through this example, here's a file you can use: real-estate-listings.csv. Upload the file to HubDB, in order to follow along. 

Publish your table, making note of the ID of the table (the last number in the URL) and you're ready to start using your data.


Create a Simple Listing Page

The first thing we can do with this data is to create a simple listing using HubL and the HubDB functions.

First, we will need to create a new template in Design Manager (if you haven't already) and a new Custom Module. You can name it something like "Real Estate Module". We want to generate a Custom Module to allow for easier use across multiple templates or allow for potential custom fields to be created, depending on your needs. 

Within your new Custom Module, add the following code. This will generate our new Simple Listing Page.

{% set table = hubdb_table_rows({Insert YOUR Table ID}, queryparam) %} {% if table == [] %} <p class='align-center'>Sorry, no listings found for that Search. Try changing your filter and search again.</p> {% else %} {% for row in table %} <div> <div class="{{ row["type"].name }}"> <div><img src="{{ row["listing_image"].url}}"></div> <div> <h2>{{ row["listing_price"] }}</h2> <h3>{{ row["listing_address"] }}</h3> <h4>{{ row["type"].name }}</h4> <div> <ul> <li><span>{{ row["listing_bedrooms"] }}</span><br>bd</li> <li><span>{{ row["listing_baths"] }}</span><br>ba</li> <li><span>{{ row["listing_sq_ft"] }}</span><br>sq ft</li> <li><span>{{ row["listing_acres"] }}</span><br>acres</li> </ul> </div> <div class="button"><a href="{{ row["listing_url"] }}">Details</a></div> </div> </div> </div> {% endfor %} {% endif %}

Click over to the preview of the template and you should see the simple Real Estate Listing. If nothing appears, double-check that your table ID is correct and that the table is published.

Let's breakdown what we are doing here. Don't worry about the 'queryparam' just yet, this will be needed for later in the tutorial when we filter our Table based on our visitors search input to filter on our listings!

First, we add a little logic to render an error if for some reason our table returns an empty object. Next, we map out the rows to our Listing page. If done correctly, our preview should be rendering our Listing Database. Feel free to modify the HTML structure as needed. 

It is worth noting that we are accessing the values of our HubDB rows via bracket notation. It is also perfectly acceptable to adjust this to use 'dot' notation.

Add a Filter to the Listing Page

We are off to a great start, however, there is an issue here. Our visitors won't always want to look through ALL of our listing results. Often, they may only be interested in a specific type of listing. Heck, they may even want to search based on Zip-Code or City (we'll add this functionality shortly)!

So, let's help our visitors out and add in a simple Filter to our Listing Page. In this example, we are going to add a filter on our HubDB type Column. 

At the top of our Custom Module add the following code to generate a simple filter. 

<!-- set the filter by drop down, search bar, and submit button --> <div> <form id="form_id" method="get"> <div> <h4>FILTER BY LISTING TYPE: </h4> <select name="type" form="form_id" onChange="this.form.submit()"> <option value="show-all">Show All</option> {% set types = hubdb_table_column({Insert YOUR Table ID}, "type").options %} {% for choice in types %} {% set type_list = type_list~choice.id|list%} {% if choice.id == request.query_dict.type%} <option selected="selected" value="{{ choice.id }}">{{ choice.name }}</option> {% else %} <option value="{{ choice.id }}">{{ choice.name }}</option> {% endif %} {% endfor %} </select> </div> </form> </div>
To generate our filter we are creating an HTML form element, which will be mapped to the values of our HubDB Type field, setting a new variable called 'types'. Next, we use a for loop to loop through the values of our field. This is used in the last bit of logic to set the value and label pair for the select field options.

Add Logic to Handle Query Parameter Filters

Now that we have a simple filter on the page, we need to write some logic to handle the rendering the Listing page, based on this input. 

We can do this with a few If Requests that set our Filter Parameter that will be passed into the table. Insert the following code into your Custom Module, after your Form object, but before, the main HubDB table. 

<!-- sets the different query parameters using submitted input for hubdb query --> {% set queryparam = "" %} {% if request.query_dict.type in ["1", "2", "3", "4"] %} {% set queryparam = queryparam ~ "&type="~request.query_dict.type|urlencode %} {% endif %} {% if request.query_dict.type == "show-all" %} {% set queryparam = queryparam %} {% endif %}

Remember the queryparam variable from the beginning of this tutorial? Let's put it to use! This code introduces an if statement. Using the request object on the CMS page we can determine if the filter option, selected by the visitor, is in our Array. If it is then we set 'queryparam' equal to that option. When this value is appended as our Table's filter option, our Listing page will update to reflect only the listing's the customer wants to see! Go ahead and generate a page from your template and see everything work in action! 

Add A Search Field To The Listing

Our listing page is OK, but especially if the list got much bigger, it would be nice to show the visitor Listing results based on a search query. We can do this by adding in a search field and then modifying our logic slightly. 

Add the following HTML before the closing </form> tag.

... <div> <input name="listing_address" type="text" id="search-by" class="autocomplete" placeholder="Search by City or Zip Code..."> </div> <input id="submit-button" type="submit" value="search"> ...

This input field will allow our visitors the ability to search across multiple components within our "listing_address" column. Using some placeholder text we can indicate what the search should be used for. In this case, we want our users to know they can return results based upon a Zip Code or City query. 

Complete The Query Parameter Logic

In order to allow the user's search to function, we will need to slightly modify the logic we defined above, within Step 4. Replace the code that we originally generated within the following, updated, version.

... {% set queryparam = "" %} {% if request.query_dict.type in ["1", "2", "3", "4"] and request.query_dict.listing_address == "" %} {% set queryparam = queryparam ~ "&type="~request.query_dict.type|urlencode %} {% endif %} {% if request.query_dict.type in ["1", "2", "3", "4"] and request.query_dict.listing_address != "" %} {% set queryparam = queryparam~"&type="~request.query_dict.type|urlencode~"&listing_address__icontains="~request.query_dict.listing_address|urlencode %} {% endif %} {% if request.query_dict.type == "show-all" and request.query_dict.listing_address != "" %} {% set queryparam = queryparam~"&listing_address__icontains="~request.query_dict.listing_address|urlencode %} {% endif %} ...

In order to support our visitor's search query, we need to add a little more logic. These new If statements accomplish that. In each statement, we are adding an add condition that is checking for the presence of the search field being submitted. First, if there is nothing passed in the search field and the page is being filtered, then we only set a query on the type field. The rest of the logic ensures that we modify the request's queryparam if there is a search term present. 

We do this with the use of the '__icontains' HubDB filter. This is also why we originally used a 'text' field to contain our Listing Address. If the user inputs a Zip Code or City that is within this column, then the proper rows will be returned to our HubDB Table. 

Thats it!

At this point, we have a fully working Real Estate Listing Page! Complete with the ability for our user to Filter by listing type AND to even Search across our listings, by Zip Code, or even City! 

Here is an example page that we built out using this example: Real Estate Listing Example

Moving forward this example can be modifed even more. What if you wanted to utilize the location service of HubDB to plot your listings on a Map? This can certainly be achieved with the power of HubDB and by modifying the tutorial seen here: Build a Map Using HubDB