TDS provides a extensible and customizable user interface using Thymeleaf Java template engine. The HTML pages which currently support customization are:
- Catalog
- Dataset access
UI customization can be implemented through the contribution of CSS stylesheets and Thymeleaf HTML templates. To promote the accessibility and usability of dataset, TDS administrators may register Jupyter Notebooks as dataset viewers using the Jupyter Notebook service.
CSS Stylesheets
To customize the TDS using CSS, contributed CSS documents should be placed inside the ${tds.content.root.path}/thredds/public
directory.
By default, the TDS is configured to use several CSS documents supplied in the public
directory in threddsConfig.xml
.
There are four properties within the htmlSetup
element used to define stylesheets:
<htmlSetup>
<standardCssUrl>standard.css</standardCssUrl>
<catalogCssUrl>catalog.css</catalogCssUrl>
<datasetCssUrl>dataset.css</datasetCssUrl>
<openDapCssUrl>tdsDap.css</openDapCssUrl>
</htmlSetup>
To override HTML style default the TDS, replace any or all of the standard stylsheets with your own. Each property is responsible for style on a set of HTML pages:
standardCssUrl
: styles are applied on all generated HTML pages (except the OPeNDAP HTML form)catalogCssUrl
: styles are applied only on Catalog HTML pagesdatasetCssUrl
: styles are applied only on Dataset access HTML pagesopenDapCssUrl
: styles are applied only on the OPeNDAP HTML form; unlike other pages, this is the only CSS document applied on the page
Thymeleaf Templates
When the TDS is deployed, a templates
directory is created within the main content
directory (tds.content.root.path
).
Each time a customizable HTML page is requested (Catalog or Dataset), the Thymeleaf template resolver will search this directory
for user-supplied template fragments.
Pages are customizable at plug-in points defined by the tag ext:
, which instructs the template resolver to look for
externally supplied template fragments.
Some of the plug-in points provide defaults when no user-supplied template is available (such as the main TDS header and footer,
whereas other plug-in allow for additional content.
See the Thymeleaf documentation for an overview of natural templating using Thymeleaf and fragments.
A full list of currently supported plug-in points for user-supplied fragments can be found in the following sections.
Overwriting a default
To contribute a template fragment, place the fragment
element in templates/tdsTemplateFragments.html
.
Example: overwriting the default header
Add the following to the ‘template/tdsTemplateFragments.html’ file:
<div th:fragment="header">Your header content here</div>
The templating system will automatically attach default TDS CSS properties to custom headers and footers. To avoid this behavior, users must provide their own overrides through custom stylesheets.
Current default fragments which allow overrides are:
header
footer
Contributing additional content sections
Users may contribute additional content sections the same way as overridable defaults; unlike sections with default content, i.e. headers and footers), additional content sections are optional and will only render as HTML elements if a user-contributed template fragment exists.
Example: adding content to the bottom of the Catalog
Add the following to the ‘template/tdsTemplateFragments.html’ file:
<div th:fragment="datasetCustomContentBottom">
<div>Your bottom content goes here.</div>
</div>
Example: contributing multiple fragments
To add multiple fragments to a customizable section, add the following to the ‘template/tdsTemplateFragments.html’ file:
<div th:fragment="datasetCustomContentBottom">
<div th:replace="~{ext:additionalFragments/myFragments :: mySectionHeader}"/>
<div th:replace="~{ext:additionalFragments/myFragments :: mySectionContent}"/>
</div>
And, in the templates/additionalFragments/myFragments.html
file:
<div th:fragment="mySectionHeader" class="section-header">My Section Name</div>
<div th:fragment="mySectionContent" class="section-content">Your contributed content here.</div>
In the example above, we have defined our own fragments in a separate file, myFragments.html
.
Fragments which correspond to a plug-in point, such as catalogCustomContentTop
must be within the file tdsTemplateFragments
,
however main fragments may reference paths to unlimited other template files by using the ext:
tag.
Note: The classes section-header
and section-content
apply the default TDS style for content panes.
Currently supported contributable sections are:
catalogCustomContentTop
- additional content placed at the top of catalog pages.catalogCustomContentBottom
- additional content placed at the bottom of catalog pages.datasetCustomContentBottom
- additional content placed at the bottom of dataset access pages.
Contributing additional content tabs
Contributing tabbed content requires two fragments, one for the tab button and another for the content.
Each tab button must implement the click event handler switchTab(buttonElement, contentElementId, groupId)
.
Example
Add the following to the ‘template/tdsTemplateFragments.html’ file:
<div th:fragment="customInfoTabButtons">
<div class="tab-button info" onclick="switchTab(this, 'custom1', 'info')">Custom1</div>
<div class="tab-button info" onclick="switchTab(this, 'custom2', 'info')">Custom2</div>
</div>
<div th:fragment="customInfoTabContent">
<div class="tab-content info" id="custom1">This is one contributed tab pane...</div>
<div class="tab-content info" id="custom2">..and this is a second!</div>
</div>
In the above example, the tab-button
and tab-content
classes apply the same style to the contributed tabs as the
default tabs.
The info
class groups the contributed tabs with the other tabs in the information tab pane.
To group a contributed tab with the access tab pane, use the access
class.
Note: Multiple custom tabs may be contributed by grouping them within the fragment tags.
Current contributable tabs are:
customAccessTabButtons/customAccessTabContent
- adds tabs to the tab pane holding the “Access” and “Preview” views.customInfoTabButtons/customInfoTabContent
- adds tabs to the tab pane holding view with information about the dataset.
Accessing TDS properties in custom templates
Information from the server is passed to the templated pages through a data model. The properties made available to the template parser are:
{
String googleTracking,
String serverName,
String logoUrl,
String logoAlt,
String installName,
String installUrl,
String webappName,
String wabappUrl,
String webappVersion,
String webappBuildTimestamp,
String webappDocsUrl,
String contextPath,
String hostInst,
String hostInstUrl
}
Additionally, the catalog page is passed the properties boolean rootCatalog
, which is set to true
only on the top-level catalog page,
and List<CatalogItemContext> items
, a set of items in the Catalog defined as CatalogItemContext
data contracts:
class CatalogItemContext {
String getDisplayName();
int getLevel();
String getDataSize();
String getLastModified();
String getIconSrc();
String getHref();
}
Similarly, the dataset page is passed the property DatasetContext dataset
, a data contract defining the properties of the dataset:
class DatasetContext {
String getName();
String getCatUrl();
String getCatName();
List<Map<String, String>> get Documentation();
List<Map<String, String>> getAccess();
List<Map<String, String>> getContributors();
List<Map<String, String>> getKeywords();
List<Map<String, String>> getDates();
List<Map<String, String>> getProjects();
List<Map<String, String>> getCreators();
List<Map<String, String>> getPublishers();
List<Map<String, String>> getVariables();
String getVariableMapLink();
Map<String, Object> getGeospatialCoverage();
Map<String, Object> getTimeCoverage();
List<Map<String, String>> getMetadata();
List<Map<String, String>> getProperties();
Map<String, Object> getAllContext();
Object getContextItem(String key);
List<Map<String, String>> getViewerLinks();
}
Example: Dataset view
Add a section to a dataset view which links to the host institution site and displays a table of all properties returned by getAllContext()
.
Add the following to the ‘template/tdsTemplateFragments.html’ file:
<div th:fragment="datasetCustomContentBottom">
<h3>Properties of
<th:block th:text="${dataset.getName()}"
- hosted by <a th:href="${hostInstUrl}" th:text="${hostInst}"></a>
</h3>
<table class="property-table">
<tr th:each="prop : ${dataset.getAllContext()}">
<td><em th:text="${prop.key}"/><td th:text="${prop.value}"/>
</tr>
</table>
</div>
Contributing to the TDS: adding accessible properties
Don’t see what you’re looking for?
If the properties exposed to the template parser do not meet your needs, you are encouraged to update the above data models by submitting a pull request to
the Unidata TDS GitHub repository.
The data models are defined and populated in
CatalogViewContextParser.java
.
Jupyter Notebooks
About
The goal of the Jupyter Notebook service is to provide a method of interacting with and visualizing TDS datasets without
large data transfers.
When the Notebook service is enabled, the service provides a list of available Notebooks the demo access to requested dataset via Siphon.
The service returns requested Notebooks as ipynb
files, which may be viewed in Jupyter Notebook or JupyterLab and edited by the end user to
explore capabilities of the dataset and Siphon.
Read more about Jupyter Notebooks here.
Enable/Disable Notebook Service
By default, the Jupyter Notebook service is enabled. To disable the Notebook service, add the following property to threddsConfig.xml
:
<JupyterNotebookService>
<allow>false</allow>
</JupyterNotebookService>
To configure the Notebook service, add the following properties to threddsConfig.xml
:
<JupyterNotebookService>
<allow>true</allow>
<maxAge>60</scour>
<maxFiles>100</maxFiles>
</JupyterNotebookService>
Where <maxAge>
defines how long a mapping between a dataset and a Notebook should be stored after the last access, and <maxFiles>
defines the maximum number of mappings which can be stored at one time.
The TDS provides some default Notebook viewers, which can be
Using the Notebooks Service
Accessing Notebooks through a browser
All Notebooks viewers that are valid for a given dataset can be accessed though the Dataset HTML page under the “Preview” tab.
Accessing Notebooks via code
Two public endpoints are available in the Notebook service:
- Get all valid viewers for a dataset: {hostURL}/thredds/notebook/{datasetID}?catalog={catalogURL}
- e.g. https://mysite.edu/thredds/notebook/mydataset?catalog=catalog.xml
- Download a selected viewer: {hostURL}/thredds/notebook/{datasetID}?catalog={catalogURL}&filename={filename}
- e.g. https://mysite.edu/thredds/notebook/mydataset?catalog=catalog.xml&filename=jupyter_viewer.ipynb
Custom Notebooks
To add a Notebook viewers to the TDS Notebook service, place ipynb
files in the notebooks
folder within the content directory.
(Note: To register new Notebook viewers, the server must be restarted with the new files in the notebook directory,
TDS will not process new Notebooks while active.)
Notebook viewer properties are set by adding a viewer_info
property to the Notebooks metadata block:
"metadata": {
...
"viewer_info": {
...
}
}
The Notebook services checks for two viewer properties: description
and accepts
. The description
property defines a plain-text
description of the viewer, and defaults to an empty string if not present. The accepts
property defines the set of datasets for which
the viewer is valid and may include any or all of the following sub-properties:
accept_datasetIDs
: Accepts a list of dataset IDs for which the Notebook is valid.accept_catalogs
: Accepts a list of catalog names or URLs which contain datasets for which the Notebook is valid.accept_dataset_types
: Accepts a list of feature types for which the Notebook is valid (e.g. Grid, Point, Station).accept_all
: If true, indicates the Notebook is valid for all datasets.
If no accepts
properties are included in the Notebook metadata, the Notebook will default to "accept_all": true
.
Examples
A Notebook configured for all datasets in the catalog testCatalog
:
"metadata": {
...
"viewer_info": {
"description": "This Notebook displays all datasets in the test catalog.",
"accepts": {
"accept_catalogs": ["testCatalog"],
}
}
}
A Notebook configured for all gridded datasets and a dataset called almostGridded
"metadata": {
...
"viewer_info": {
"description": "This Notebook displays gridded data.",
"accepts": {
"accept_datasetIDs": ["almostGridded"],
"accept_dataset_types": ["Grid"]
}
}
}
The accept_datasetIDs
can also include regular expressions. This can be useful, for instance, when configuring a
notebook for all datasets in a datasetScan
:
"metadata": {
...
"viewer_info": {
"description": "Notebook that displays all datasets in a dataset scan.",
"accepts": {
"accept_datasetIDs": ["myDatasetScanID/.*"],
}
}
}
Suppressing default Notebooks
To suppress default Notebooks, you can override them with a custom Notebook or a dummy Notebook, configured to not accept any datasets.
For example, to suppress default_viewer.ipynb
, place a file of the same name in the content directory with the following viewer_info
:
"metadata": {
...
"viewer_info": {
"accepts": {
"accept_all": false
}
}
}
Contributing default Notebooks
You can contribute default Notebooks viewers to the TDS repository to highlight various types of datasets by submitting a pull request to
the Unidata TDS GitHub repository.
The default Notebooks live in the jupyter_notebooks
directory.
NOTE: Be sure to map your contributed Notebooks to the appropriate datasets by editing the Notebook’s metadata, as described above.