TDS provides a extensible and customizable user interface using Thymeleaf Java template engine. The pages which currently support customization are:

  • Catalog
  • Dataset access

UI customization can be implemented through the contribution of both CSS stylesheets and Thymeleaf HTML templates.

The public Directory

Certain files in the ${tds.content.root.path}/thredds/public directory are automatically mapped and served from the TDS context root.

For Example:

${tds.content.root.path}/thredds/public/logo.png

will be available as

https://you.server.name/thredds/logo.png

Allowed Files

The TDS currently will serve the following files if they are placed in ${tds.content.root.path}/thredds/public:

  • .css
  • .gif
  • .jpg
  • .png
  • .jsp
  • sitemap.xml

CSS Stylesheets

To customize TDS using CSS, custom CSS documents should be placed inside the aforementioned ${tds.content.root.path}/thredds/public directory.

TDS is configured to use the CSS documents supplied in the public directory in threddsConfig.xml. There are three 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>

CSS documents given for catalogCssUrl and datasetCssUrl elements will be applied on the Catalog and Dataset access HTML pages, respectively. A CSS document supplied to standardCssUrl will be used in all generated HTML pages. The OPeNDAP HTML form is treated special – the only CSS document applied to this page is defined by the openDapCssUrl element.

Thymeleaf Templates

When TDS is deployed, a templates directory is created within the main content directory (tds.content.root.path). The Thymeleaf template resolver used by TDS will search this directory for user-supplied template fragments each time a customizable HTML page is requested.

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 current 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 may be overridden are:

  • header
  • footer

Contributing Additional Content Sections

Additional content sections may be contributed the same way as overridable defaults, but will only render as HTML element if a user-contributed template fragment is found.

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

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>

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 the main fragments may reference paths to other template files by using the ext: tag.

Note: The classes section-header and section-content apply the default TDS style for content panes.

Current 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.

Using TDS Properties In Contributed 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 Catalogdefined 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 getVaraiableMapLink();

  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>

Adding TDS Properties To Templates

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 https://github.com/Unidata/thredds. The data models are defined and populated in CatalogViewContextParser.java.