Airtable in Automad — a fexible yet simple to use Automad extension that let’s you easily integrate Airtable bases into your site by using Handlebars templates.
Airtable is a great tool to quickly create your own database using a intuitive UI. While the possibilities of structuring data go far beyond the capabilities of Automad as a blogging platform, you might find out that Airtable lacks of flexibility and design options when it comes to sharing tables publicly. This is where Airmad comes in. The concept is rather simple. Airmad pulls a table — and optionally also its linked tables — using Airtable’s REST API. To speed things up and align them with the user experience of a small and lightweight Automad site, all retrieved recordes are cached on your server. Updated data is pulled from time to time.
Attention
Airmad requires your webserver to run PHP 7+ in order to work properly!
Airmad can be installed by using the Automad dashboard. However in case you would like to install the package by using Composer, just run the following command on your command line:
$ composer require airmad/airmad
Airtable requires an API token to authenticate when
accessing bases using their REST API. In case you don’t have one, you can easily
create one on your Airtable profile page. After successfully creating such token,
it has to be added to Automad’s config/config.php
file. That can be done by
navigating to System Settings > More > Edit Configuration File in the Automad
dashboard as demonstrated below. Aside from the authentictaion, there you can also
configure the Airtable cache lifetime in seconds.
{
"AIRMAD_TOKEN": "keyXXXXXXXXXXXXXX",
"AIRMAD_CACHE_LIFETIME": 43200,
...
}
Airmad can either be used in template files as part of a theme or, as recommended, in a snippet block. The latter one allows for integrating Airmad into any existing theme that supports Automad’s block editor. The markup looks as follows:
Attention
You can simply paste an Airmad snippet directly into a code field of the new Template Snippet block on any page in the Automad dashboard.
<@ Airmad/Airmad {
base: 'appXXXXXXXXXXXXXX',
table: 'Products',
view: 'Grid view',
linked: 'Type',
template: '
<div class="card">
<div class="card-content uk-panel uk-panel-box">
<div class="uk-panel-title">
{{ fields.Name }}
</div>
<p>
{{# fields.@.Type }}
<i>{{ Name }}</i>
{{/ fields.@.Type }}
</p>
</div>
</div>
',
filters: 'Name, Type',
limit: 20,
page: @{ ?page | 1 }
} @>
The code above doesn’t produce any output. Instead it populates some Runtime
variables that can be used in the
Automad template to at any point after the Airmad instance above.
To display the generated output, the :airmadOutput
variable can be used in a
template for example as follows.
<div class="cards grid am-stretched">
@{ :airmadOutput }
</div>
Attention
In case you want to use multiple Airmad instances on one page, you will have to define unique prefixes for each one in order to avoid conflicts between them. Read more about using multiple instances below.
The example above shows a typical use case of an Airtable integration. Find below a list of all availabe options.
Name |
Description |
---|---|
|
The Airtable base ID |
|
The main table to be used to pull records from |
|
The view of the main table to be used |
|
A comma separated list of tables that are linked to a field
of the main table records — note that is only required to list linked tables
here that include information that you want to display. In case the field name
differs from the actual table name to be linked, it is also possible to pass
a list of strings like |
|
The Handlebar template to be used to render a record — can be either a string, a variable containing a string or a file path |
|
A comma separated list of fields that can be used to filter the records by — check out the examples below for more information about filtering |
|
The maximum number of records to be displayed on a page |
|
The current page of records (pagination) |
|
An optional prefix for the generated runtime variables instead of the
default |
Aside from the output, Airmad provides more variables as shown in the table below.
Name |
Description |
---|---|
|
The rendered output of the table records |
|
The number of found records |
|
The current page number — this has to be seen in context to
the |
|
The amount of pages the records are spread over,
also related to the |
|
The max memory used by Automad in bytes |
Attention
Note that you can define an unique prefix to be used instead of :airmad*
in the
Airmad options when creating a new instance.
As soon as you want to use filters and select dropdowns to let a user control the displayed
set of records on a page, you will have to use multiple instances of Airmad on one page.
For example one instance request all records of a fictional table called Type
to generate a list of all existing product types in your database, while another one
gets the actual products for example from a table called Products
.
To avoid overwriting the output the first table with the output of the second one,
the generated runtime variables need to have a unique prefix that can be defined in the
options by using the prefix
parameter.
<@ Airmad/Airmad {
base: 'appXXXXXXXXXXXXXX',
table: 'Type',
view: 'Grid view',
template: '<option value="{{ fields.Name }}">',
prefix: ':type'
} @>
@{ :typeOutput }
<@ Airmad/Airmad {
base: 'appXXXXXXXXXXXXXX',
table: 'Products',
view: 'Grid view',
template: '<option value="{{ fields.Name }}">',
prefix: ':products'
} @>
@{ :productsOutput }
As mentioned earlier, Airmad uses Handlebars
templates to render record data. While iterating table records, all record data is exposed to the engine
and can be accessed by using the normal variable tags.
The main items here are the id
, the fields
and the createdTime
.
The fields
item actually contains all table fields entered by you. For example to get the Name
of a record,
you can simply use {{ fields.Name }}
in a template.
Aside from the default tags, Airmad provides some other useful helpers to let you easily use fields in
linked tables or build slideshow.
In case your table has an attachement field, you can use the {{#slider fields.images}}
helper function to
create an image slider containing all provided images as that are listed in a field called fields.images
.
By default the slide will have an aspect ratio of 1:1 — in other words a height of 100% relative to the width.
You can pass an optional second argument to the helper to define a custom height as follows:
{{#slider fields.images 75%}}
In case you have fields that actually link to other tables in your base, the content of such a field is just a
bunch of record IDs. In most cases you would want to be able to actually get the values of the one or more
fields of that record. Therefore Airmad adds a dedicated fields to your data model at runtime called fields.@
.
The @
field contains all referenced records in linked tables. The example below demonstrates the usage of such fields.
To simply get the IDs of the records in a linked table, you can just loop over the list of IDs as usual.
{{# fields.Type }}
{{ . }}
{{/ fields.Type }}
Instead of just getting the ID, you can directly loop over a list of the linked records by inserting a @
into {{# fields.Type }}
like {{# fields.@.Type }}
. It is important to understand that the name after the @
is here again the name of the field and might differ from the actual table name.
{{# fields.@.Type }}
<i>{{ Name }}</i>
{{/ fields.@.Type }}
When building dropdown menus or similar to filter the set of elements, it is imortant to know what filter is currently active.
Therefore Airmad the active
field to any record that appears as value for a table filter in the query string.
The field can be used as follows:
<option value="{{ id }}" {{#if active}}selected{{/if}}>
{{ fields.Name }}
</option>
In many cases, the amount of records in a table is simple to much for a single page.
You will probably break down the list of records into multiple pages by setting the limit
option to a fixed number. To help you building a simple pagination navigation,
Airmad provides the :airmadPage
and :airmadPages
runtime
variables.
A very simple example for a pagination within an Automad snippet could look as follows:
<ul class="uk-pagination">
<@ if @{ ?Page } > 1 @>
<li><a href="?<@ queryStringMerge { Page: @{ ?Page | -1 } } @>">←</a></li>
<@ end @>
<@ for @{ :airmadPage | -3 } to @{ :airmadPage | +3 } @>
<@ if @{ :i } > 0 and @{ :i } <= @{ :airmadPages } @>
<li>
<a
href="?<@ queryStringMerge { Page: @{ :i } } @>"
<@ if @{ ?Page | def(1) } = @{ :i } @>class="uk-active"<@ end @>
>
@{:i}
</a>
</li>
<@ end @>
<@ end @>
<@ if @{ ?Page } < @{ :airmadPages } @>
<li><a href="?<@ queryStringMerge { Page: @{ ?Page | +1 } } @>">→</a></li>
<@ end @>
</ul>
You can simply copy and paste this into a snippet block after creating an Airmad instance. The classes in use will work out of the box with the Standard and Adam themes.
Searching and filtering are essential functions for displaying database content. In Airmad filtering records is pretty straight forward. The following example demonstrated the basic idea:
<form action="">
<input type="text" name="Name" value="@{ ?Name }">
<input type="text" name="Type" value="@{ ?Type }">
</form>
<ul>
<@ Airmad/Airmad {
base: 'appXXXXXXXXXXXXXX',
table: 'Products',
view: 'Grid view',
linked: 'Type',
template: '<li>{{ fields.Name }}</li>',
filters: 'Name, Type',
limit: 20,
page: @{ ?Page | def(1) }
} @>
</ul>
In the snippet above, we have a simple form at the top including two input fields
with the names Name
and Type
. The Airmad instance below that form has those names defined as filters
as you
can see in the highlighted line. Note that since in this example Type is a linked table, defining the linked
parameter
allows for searching in linked records as well.
To enhance the user experience for your visitors, you might want to provide an autocompletion list of Type names
for the second input field. You can simply use a second Airmad instance to pull all type names from the Type table and
populate such a list with the Name
field of each record. In the following example we use a datalist for such purpose.
Warning
Note in the snippet below that this time the prefix parameter must be set to a unique value to avoid conflict between both Airmad instances.
<form action="">
<input type="text" name="Name" value="@{ ?Name }">
<input type="text" list="types" name="Type" value="@{ ?Type }">
<@ Airmad/Airmad {
base: 'appXXXXXXXXXXXXXX',
table: 'Types',
view: 'Grid view',
template: '<option value="{{ fields.Name }}">',
prefix: ':type'
} @>
<datalist id="types">
@{ :typeOutput }
</datalist>
</form>
<ul>
<@ Airmad/Airmad {
base: 'appXXXXXXXXXXXXXX',
table: 'Products',
view: 'Grid view',
linked: 'Type => Types',
template: '<li>{{ fields.Name }}</li>',
filters: 'Name, Type',
limit: 20,
page: @{ ?Page | def(1) }
} @>
</ul>
The following example is supposed to wrap all features of Airmad like getting records, filtering and building a pagination. To allow for quick testing, the base for this example is the Project tracker database that serves as sample content when creating a new account on Airtable. Therefore it should be easy to just copy and paste the code — by replacing the app ID of course — after setting up authentication.
Attention
Make sure that you already have added the AIRMAD_TOKEN to your configuration as described in the Get Stared guide. And don’t forget to replace the base ID in the snippet below with the one in your API documentation!
To be easily understandable, this example code is boken down into three section. You can simply paste all sections together into a Template Snippet block.
The first part is creating the filter menu. Note that the naming of the input fields is essential here! In our example we want to filter the records by the Client field that is linked to the Clients table (note the “s” in the table name).
<form action="">
<input type="text" name="Name" value="@{ ?Name }">
<input type="text" list="clients" name="Client" value="@{ ?Client }">
<@ Airmad/Airmad {
base: 'appXXXXXXXXXXXXXX',
table: 'Clients',
view: 'All clients',
template: '<option value="{{ fields.Name }}">',
prefix: ':clients'
} @>
<datalist id="clients">
@{ :clientsOutput }
</datalist>
<button type="submit">Filter</button>
</form>
The second part is building the actual list of record. Again, the mapping of linked tables is important here! The code below will generate a grid of record including a little slider to be used with with the Standard theme.
<@ Airmad/Airmad {
base: 'appXXXXXXXXXXXXXX',
table: 'Design projects',
view: 'All projects',
linked: 'Client => Clients',
template: '
<div class="card">
<div class="card-content uk-panel uk-panel-box">
<div class="uk-panel-teaser">
{{#slider fields.Project images 75%}}
</div>
<div class="uk-panel-title">
{{ fields.Name }}
</div>
<p>
{{# fields.@.Client }}
<b>{{ Name }}</b>
{{/ fields.@.Client }}
</p>
</div>
</div>
',
filters: 'Name, Client',
limit: 8,
page: @{ ?Page | def(1) }
} @>
<div class="cards grid am-stretched">
@{ :airmadOutput }
</div>
The last part will create the pagination navigation. Again the generated markup will work out of the box with the Standard theme.
<ul class="uk-pagination">
<@ if @{ ?Page } > 1 @>
<li><a href="?<@ queryStringMerge { Page: @{ ?Page | -1 } } @>">←</a></li>
<@ end @>
<@ for @{ :airmadPage | -3 } to @{ :airmadPage | +3 } @>
<@ if @{ :i } > 0 and @{ :i } <= @{ :airmadPages } @>
<li><a href="?<@ queryStringMerge { Page: @{ :i } } @>" <@ if @{ ?Page | def(1) } = @{ :i } @>
class="uk-active"
<@ end @>>@{:i}</a></li>
<@ end @>
<@ end @>
<@ if @{ ?Page } < @{ :airmadPages } @>
<li><a href="?<@ queryStringMerge { Page: @{ ?Page | +1 } } @>">→</a></li>
<@ end @>
</ul>