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.

../../_images/logo.png

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.