diff --git a/docs/websites/website-listings-custom.qmd b/docs/websites/website-listings-custom.qmd
index 47667ee805..ae3306b86a 100644
--- a/docs/websites/website-listings-custom.qmd
+++ b/docs/websites/website-listings-custom.qmd
@@ -4,62 +4,80 @@ title: Custom Listings
## Overview
-In addition to the 3 built in types of listings, you can also build a completely custom display of the items. This custom display can generate any HTML and can optionally still take advantage of the sorting, filtering, and pagination provided by listings.
+In addition to the three built-in types of listings, you can also build a completely custom display of the items. This custom display can generate any HTML and can optionally still take advantage of the sorting, filtering, and pagination provided by listings.
## Listing Templates
-To build a custom listing display, you create an [EJS template](https://ejs.co) that will be used to generate the HTML for a set of items that are passed to the template. EJS templates allow you to generate HTML using plain javascript, making it easy to loop through items and output their values in your custom HTML.
+To use a custom template, add the `template` option to your listing configuration:
-
-To use a custom template, pass it in the `template` option for a listing:
-
-``` yaml
-listing:
- template: gallery.ejs
-```
-
-When a listing with a custom template is rendered, the listing contents will be read and processed into a set of items that are passed to the template for rendering. For example, in this case, all the documents in the posts directory will be read into items and passed to the `gallery.ejs` template.
-
-``` yaml
+```{.yaml filename="index.qmd"}
+---
listing:
contents: posts
- template: gallery.ejs
+ template: gallery.ejs.md
+---
```
-A simple template for outputing a list of documents might look like:
+Quarto passes the listing contents to the template as an array called `items`.
+Each item is populated with the fields described in [Listing Fields](website-listings.qmd#listing-fields), plus any custom metadata from the document.
+
+The template uses [EJS](https://ejs.co) to combine HTML and JavaScript (inside `<% %>` tags) to process `items`.
+For example, this template constructs a list of links by looping through `items` and accessing the `path` and `title` fields:
-``` html
+````{.html filename="gallery.ejs.md"}
+```{=html}
```
+````
-which produces simple HTML output like:
+The result is a simple bulleted list of links to the documents in the listing:
-{.border}
+{.border fig-alt="A custom listing output showing a list of document titles as links."}
-When rendered, the above template will receive an array of listing items called `items`. When the contents of a listing are loaded from a list of documents, each of those items will be populated with the fields described in [Listing Fields](website-listings.qmd#listing-fields). In addition, any other fields included in a documents metadata will be passed as a property of the item, making it possible to use custom metadata in your documents and the listing display.
-::: {.callout-note}
+Quarto processes EJS templates as Markdown, so we recommend using the `.ejs.md` extension for your template files. Since most templates output HTML, wrap your HTML in a raw block (`` ```{=html} ``) to avoid performance issues and escaping problems.
-Note that Quarto uses `lodash` to render the EJS templates. The `lodash` uses different syntax for HTML escaping text in templates.
+If you want a field like `title` to support Markdown formatting (*e.g.*, `My **bold** title`), exit the raw HTML block to output the Markdown content, then re-enter it:
+````{.html filename="gallery.ejs.md"}
+```{=html}
+
+```
````
+
+::: {.callout-note}
+## HTML Escaping
+
+Use `<%- value %>` to escape HTML characters in a value, or `<%= value %>` to output without escaping:
+
+````ejs
HTML escaped value: <%- value %>
HTML unescaped value: <%= value %>
````
+This syntax comes from `lodash`, which Quarto uses to render EJS templates.
:::
## Metadata Listings
The `contents` option for a listing most commonly contains a list of paths or globs, but it can also contain metadata. When contents are metadata, the metadata will be read into items and passed to the template. For example:
-``` yaml
+```{.yaml filename="index.qmd"}
+---
listing:
- template: custom.ejs
+ template: custom.ejs.md
contents:
- name: First Item
href: https://www.quarto.org
@@ -67,11 +85,12 @@ listing:
- name: Second Item
href: https://www.rstudio.org
custom-field: A second custom value
+---
```
could be rendered using:
-```` html
+````{.markdown filename="custom.ejs.md"}
```{=html}
<% for (const item of items) { %>
@@ -92,16 +111,18 @@ which produces a simple HTML display like:
The `contents` option for a listing can also point to one or more `yaml` files (which contain metadata). In that case, the metadata will be read from the files into items and passed to the template. For example:
-``` yaml
+```{.yaml filename="index.qmd"}
+---
listing:
- template: custom.ejs
+ template: custom.ejs.md
contents:
- items.yml
+---
```
where the contents of `items.yml` is:
-``` yaml
+```{.yaml filename="items.yml"}
- name: First Item
href: https://www.quarto.org
custom-field: A custom value
@@ -115,26 +136,31 @@ where the contents of `items.yml` is:
Portions of this website are built using custom listings. The best place to start is with [our gallery](/docs/gallery/index.qmd), which is a listing built using a custom template and a metadata file. You can view the source code used to create the gallery page in our [Github repository](https://github.com/quarto-dev/quarto-web).
| File | Description |
-|-------------------------------------|-----------------------------------|
+|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------|
| [gallery.yml](https://github.com/quarto-dev/quarto-web/blob/main/docs/gallery/gallery.yml) | The metadata that controls what items are displayed in the gallery listing. |
| [gallery.ejs](https://github.com/quarto-dev/quarto-web/blob/main/docs/gallery/gallery.ejs) | The template used to display the items on the page. |
| [index.qmd](https://github.com/quarto-dev/quarto-web/blob/main/docs/gallery/index.qmd) | The Quarto document that configures and positions the listing in the `#gallery` div. |
: {tbl-colwidths="\[30,70\]"}
+::: {.callout-note}
+The `gallery.ejs` template wraps all content in a raw HTML block, so Markdown formatting is not processed.
+:::
+
## Sorting, Filtering, and Pagination
-By default, sorting, filtering, and pagination are disabled for custom listings templates, but with some simple changes to your template and listing options, you can add this capability to your custom listing. To do this, you need to include the following three things in your custom template:
+By default, sorting, filtering, and pagination are disabled for custom listing templates. To enable them, include the following in your template:
1. Include a `list` class on the HTML tag that contains the list of items.
-2. For each item, include `<%= metadataAttrs(item) %>` in the HTML tag that contains the item. This will allow Quarto to write custom attributes that are used for sorting and filtering.
+2. For each item, include `<%= metadataAttrs(item) %>` in the HTML tag that contains the item. Quarto uses these attributes for sorting and filtering.
3. Within each item, include a class that identifies the tag whose text represents the contents of an item's field. The class must be the name of the field prefixed with `listing-`, for example the tag whose inner text is the `item.name` should include a class `listing-name`.
-For example, we can modify the above `custom.ejs` template as follows:
+For example, modify the above `custom.ejs.md` template as follows:
-``` html
+````{.markdown filename="custom.ejs.md"}
+```{=html}
<% for (const item of items) { %>
- >
@@ -144,14 +170,17 @@ For example, we can modify the above `custom.ejs` template as follows:
<% } %>
```
+````
Once you have included these items in your template, you can then enable the options in your listing:
-``` yaml
+```{.yaml filename="index.qmd"}
+---
listing:
sort-ui: true
filter-ui: true
page-size: 10
+---
```
The UI elements will now appear on the page and should interact properly with your custom listing.
@@ -160,9 +189,10 @@ The UI elements will now appear on the page and should interact properly with yo
You may want to provide a custom display name for your field to provide a better name than the field name. For example, the field name would appear in the sort UI. You can use `field-display-names` to create mapping from a field to a display name. For example:
-``` yaml
+```{.yaml filename="index.qmd"}
+---
listing:
- template: custom.ejs
+ template: custom.ejs.md
contents:
- items.yml
sort-ui: true
@@ -171,6 +201,7 @@ listing:
field-display-names:
name: "Name"
custom-field: "Custom"
+---
```
### Date Sorting and Formatting
@@ -179,26 +210,30 @@ To properly format and sort date values, you can specify type information for fi
You can specify field types as follows:
-``` yaml
+```{.yaml filename="index.qmd"}
+---
listing:
- template: custom.ejs
+ template: custom.ejs.md
contents:
- items.yml
field-types:
custom-date: date
custom-number: number
+---
```
### Required Fields
Since listings are generated using fields that are specified in other documents or via metadata, it can be helpful to ensure that required fields are present. You can note required fields as following:
-``` yaml
+```{.yaml filename="index.qmd"}
+---
listing:
- template: custom.ejs
+ template: custom.ejs.md
contents:
- items.yml
field-required: [name, custom-field]
+---
```
If the listing page is rendered and any of contents is missing a value for either of the required fields, an error will be thrown noting the field that is required and the file or metadata that has omitted it.
@@ -207,19 +242,23 @@ If the listing page is rendered and any of contents is missing a value for eithe
You may also make your custom template more dynamic by using parameters to control its behavior. You can provide parameters for custom templates using the `template-params` option like:
-```yaml
+```{.yaml filename="index.qmd"}
+---
listing:
- template: custom.ejs
+ template: custom.ejs.md
contents:
- items.yml
template-params:
param1: "param-value"
+---
```
-Template parameters can then be accessed in your template using `<%= templateParams.param1 %>`. For example, we can modify the above `custom.ejs` template as follows:
+Template parameters can then be accessed in your template using `<%= templateParams.param1 %>`. For example, modify the above `custom.ejs.md` template as follows:
+
+````{.markdown filename="custom.ejs.md"}
+### <%= templateParams.param1 %>
-``` html
-<%= templateParams.param1 %>
+```{=html}
<% for (const item of items) { %>
- >
@@ -228,3 +267,4 @@ Template parameters can then be accessed in your template using `<%= templatePar
<% } %>
```
+````