Serving Images
Now that we’ve learned about templates, let’s include some static assets, such as images.
Within your extension’s frontend
directory, you should see a public
directory, a package.json
file and a README.txt
file.
The Caliper SDK does not serve the images located in public
directly. When we start our server, the Caliper SDK
populates a dist
directory with the contents of the public
directory. This handled by the build
command
inside your generated package.json
.
If that’s confusing, the following example will illustrate how it works.
Note
By default, all assets in public
will be added to dist
. You can change the build command in package.json to
whatever suits your needs. Remember that only assets in dist
are accessible, and that dist
will not be
checked into your git repository. You will need to ensure your build command properly populates dist
when your
server runs for the first time in our datacenter.
The first thing we need is an image. We’ve provided a generic logo below, but feel free to use your own.

Download the image and save it to your frontend/public
directory.
Note
The image should be in the same place as the code. If you are running SDK from your local machine, you can right click on the image and download it. However, If you are remoting into the shared dev box, the following command (from shared dev box) will take care of it
$ curl https://cdn.q2developer.com/training/logo.png --output AccountDashboard/frontend/public/logo.png
Once in place, we need to make some changes to your extension code to allow the Caliper SDK and jinja to know how to find our image.
Since we are dealing with file paths, we should use the builtin python os
module.
This allows us to access the files we need in a single line of code. Go ahead and import the module at the top of extension:
import os
Replace your jinja template with the one below:
template = self.get_template('index.html.jinja2', {
'accounts': account_models,
'logo': os.path.join(self.base_assets_url, 'logo.png')
})
So that our template knows where to display the image, add the following line to the top of index.html.jinja2:
<img src="{{ logo }}" alt="Logo" height="84" width="84">
Pay special attention to the ‘logo’ key in our jinja replacements dictionary. This is where we tell the templater to replace
any {{ logo }} tags with the provided assets url. base_assets_url
automatically resolves to the root of the dist
directory.
The base_assets_url
value is available in your handler function as self.base_assets_url
or in your jinja template
as this.base_assets_url
. This makes it very easy to serve assets. To construct the URL of logo in the template you would use:
<img src="{{ this.base_assets_url }}/logo.png" alt="Logo" height="84" width="84">
For convenience, we’ve included the default method and the html to display it below.
async def default(self):
account_models = []
for account in self.account_list:
account_models.append({
'account_num': account.host_acct_id,
'account_name': account.product_name,
'account_balance': account.balance_to_display
})
template = self.get_template('index.html.jinja2', {
'accounts': account_models,
})
html = self.get_tecton_form(
"AccountDashboard",
custom_template=template,
routing_key="submit",
hide_submit_button=True
)
return html
<style>
table {
table-layout: fixed;
text-align: left;
}
td {
vertical-align: top;
}
td,
th {
padding: 15px 10px;
}
th {
box-shadow: inset 0 -2px 0 0 #cccccc;
}
td {
box-shadow: inset 0 -1px 0 0 #eeeeee;
}
.text-right {
text-align: right;
}
</style>
<img src="{{ this.base_assets_url }}/logo.png" alt="Logo" height="84" width="84">
<div class="table-wrapper">
<table>
<thead>
<tr>
<th width="150">
Host Account ID
</th>
<th>
Account Name
</th>
<th class="text-right">
Account Balance
</th>
</tr>
</thead>
<tbody>
{% for account in accounts %}
<tr>
<td>{{ account.account_num }}</td>
<td>{{ account.account_name }}</td>
<td class="text-right">{{ account.account_balance }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<q2-input
name='account_id'
id='account_id'
label="Account ID">
</q2-input>
<q2-btn intent="workflow-primary" onclick="validateForm(event)">Get Transaction History</q2-btn>
<script>
window.tecton.connected.then(function(tecton) {
const accounts = {{ accounts|tojson }}
const accountNumbers = accounts.map(account => account.account_num)
let inputField = document.getElementById('account_id')
inputField.onblur = checkForErrors
function checkForErrors(event) {
if (!accountNumbers.find(accountNumber => accountNumber == parseInt(event.target.value))){
inputField.errors = ["That's not a real account number. Please try again."]
} else {
inputField.errors = undefined
}
}
window.validateForm = function validateForm(event) {
let inputs = Array.from(document.querySelectorAll('q2-input'))
const form = document.querySelector('form')
if (inputs.find(input => input.errors)){
tecton.actions.showModal({
title: 'Form has Errors',
message: 'Please correct the fields with errors, indicated by a red triangle.',
modalType: 'error'
})
} else {
submitForm()
}
}
});
</script>
Now the image should be displayed at the top of our account list.
Next, we will take advantage of the incredible versatility of the Python language by importing and using a third-party library.