# Partial development

Develop custom partials and shortcodes following Hinode's coding conventions.

Hinode supports more than 30 shortcodes. Many of these shortcodes wrap a predefined Bootstrap component, such as the [](docs/components/button)
 or [](docs/components/tooltip)
. Custom shortcodes include a [](docs/components/command)
, [](docs/components/image)
, and [](docs/components/timeline)
. Some of these components are maintained in a separate module, such as the [](docs/components/animation)
 or [](docs/components/map)
. Hinode follows several conventions to standardize and streamline the development of shortcodes and partials. You are encouraged to use the same conventions, especially when contributing your own code for sharing.

## Shared partials

Hugo supports two kinds of reusable components, being partials and shortcodes. A shortcode can be referenced from within content files, such as Markdown. Partials can be referenced by layout files, other partials, and shortcodes too. You cannot reference a shortcode from a partial though. To enable reuse, Hinode has shifted the bulk of the implementation of most shortcodes to separate partials. These partials are maintained in the `layouts/_partials/assets` folder. The related shortcode then simply references the partial.

As an example, consider the implementation of the [](docs/components/breadcrumb)
. Hinode adds a breadcrumb to all pages (except the homepage) if enabled in the [site parameters](docs/configuration/navigation#basic-configuration)
. The implementation is available in `layouts/_partials/assets/breadcrumb.html`. The same component is also exposed as a shortcode, so it can be called from within a content page. The shortcode file `layouts/_shortcodes/breadcrumb.html` includes the following statement to invoke the partial. The `page` argument passes the current page context to the underlying partial:

```go-template
{{ partial "assets/breadcrumb.html" (dict "page" .page) }}
```

## Nested shortcodes

Several shortcodes, such as the [](docs/components/accordion)
 and [](docs/components/carousel)
, support the nesting of elements. For example, you can group multiple cards to align their heights. To enhance security, [Hinode does not process raw HTML content by default](docs/content/content-management#mixed-content)
. However, the parent shortcode `card-group` does need to further process the HTML output generated by the individual cards. To facilitate this, Hinode uses [scratch variables](https://gohugo.io/methods/shortcode/scratch) to pass trusted output from a child to its parent. These scratch variables are not accessible from within the content page, thus shielding them from any unwanted input.

Take a look at the `card` shortcode. It generates HTML content by invoking the underlying partial. If a parent is available (such as a `card-group` shortcode), it redirects or appends the partial output to the scratch variable `inner`. When no parent is available, the partial output is written to the default output stream instead. The partial output is trusted (note: the actual content processed as input by the `card` partial is **not trusted**) with the `safeHTML` pipeline instruction.

```go-template
{{ $output := partial "assets/card.html" (dict [...]) }}
{{ with .Parent }}
    {{ $current := .Scratch.Get "inner" }}
    {{ if $current }}
        {{ .Scratch.Set "inner" (print $current $output) }}
    {{ else }}
        {{ .Scratch.Set "inner" $output }}
    {{ end }}
{{ else }}
    {{ print $output | safeHTML }}
{{ end }}
```

Next, the parent `card-group` shortcode reads the scratch variable `inner` and passes this as an argument to the `card-group` partial. Each of the child `card` shortcodes should have processed the inner content. If any content remains, the `card-group` shortcode raises a warning and skips this input for further processing.

```go-template
{{ $inner := .Scratch.Get "inner" }}
{{ $input := trim .Inner " \r\n" }}
{{ if $input }}
    {{ $input = replace $input "\n" "\n  " }}
    {{ warnf "Unexpected inner content: %s\r\n      %s" .Position $input }}
{{ end }}

{{ partial "assets/card-group.html" (dict "page" .Page "cards" $inner [...]) }}
```

## Argument validation

> [!NOTE]
> Hinode has revised the argument validation in Added in v1.0.0
. Partials and shortcodes now use the utility function `utilities/InitArgs.html`. The previous function `utilities/IsInvalidArgs.html` is no longer supported.

Added in 1.0.0

Most shortcodes support multiple arguments to configure their behavior and to refine their appearance. These shortcodes share many of these arguments with an underlying partial. Hinode uses a standardized approach to validate and initialize these arguments. All arguments are formally defined in a separate data structure file. Hinode uses the YAML (Yet Another Markup Language)
 format by default, although several formats are supported. The partial `utilities/InitArgs.html` (provided by the [mod-utils module](https://github.com/gethinode/mod-utils)) then uses this specification to validate all arguments.

Let's consider the following example. The [](docs/components/toast)
 shortcode displays a dismissable message in the bottom-right corner of the screen. We can trigger it by assigning its unique identifier to a button.

```markdown
{{< button toast-id="toast-example-1" >}}Show toast{{< /button >}}

{{< toast id="toast-example-1" title="First title" >}}This is a toast message{{< /toast >}}
```

The toast shortcode displays the message `This is a toast message` provided as inner input. Additionally, it supports the following arguments:

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `class` | string |  |  | Class attributes of the element. It supports Bootstrap attributes to modify the styling of the element. |
| `id` | string |  |  | Unique identifier of the current element. |
| `title` | string |  |  | Title of the element. If the element references a (local) page, the title overrides the referenced page's title. |

The toast shortcode invokes the underlying partial to render the actual HTML output. The partial supports similar arguments, but expects the inner content to be passed as argument `message` instead. The following file formalizes these specifications:

```yml
comment: >-
  Prepares a dismissible toast notification message. Use a button trigger to
  display the message on screen.
icon: toast
arguments:
  title:
    release: v1.0.0
    preview: true
  id:
  class:
  message:
    group: partial
  # deprecated arguments
  header:
    type: string
    optional: true
    comment: Header of the toast message. Uses the site title by default.
    deprecated: v1.0.0
    alternative: title
body:
  type: string
  optional: false
  comment: Toast message.
  group: shortcode
example: |
  {{< button toast-id="toast-1" >}}Show notification{{< /button >}}
  {{< toast id="toast-1" title="Update available" >}}
    A new version has been released.
  {{< /toast >}}
```

The shortcode uses the following code to validate its arguments, excluding the `message` argument that belongs to the `partial` group. When an error occurs, the shortcode logs an error message with a reference to the context `.Position`.

```go-template
{{ $args := partial "utilities/InitArgs.html" (dict "structure" "toast" "args" .Params "named" .IsNamedParams "group" "shortcode") }}
{{ if or $args.err $args.warnmsg }}
    {{ partial (cond $args.err "utilities/LogErr.html" "utilities/LogWarn.html") (dict 
        "partial"  "shortcodes/toast.html" 
        "warnid"   "warn-invalid-arguments"
        "msg"      "Invalid arguments"
        "details"  ($args.errmsg | append $args.warnmsg)
        "file"     page.File
        "position" .Position
    )}}
    {{ $error = $args.err }}
{{ end }}
```

The underlying partial uses a similar call. Notable differences are the validated arguments (`.` instead of `.Params`) and the `group` (`partial` instead of `shortcode`). Partials are not aware of their context, so the `position` argument is dropped.

```go-template
{{ $args := partial "utilities/InitArgs.html" (dict "structure" "toast" "args" . "group" "partial")}}
{{ if or $args.err $args.warnmsg }}
    {{ partial (cond $args.err "utilities/LogErr.html" "utilities/LogWarn.html") (dict 
        "partial" "assets/toast.html" 
        "warnid"  "warn-invalid-arguments"
        "msg"     "Invalid arguments"
        "details" ($args.errmsg | append $args.warnmsg)
        "file"    page.File
    )}}
    {{ $error = $args.err }}
{{ end }}
```

## Argument and type definitions

Since Added in v1.0.0-beta3
, Hinode maintains all argument definitions in a centrally managed file within the `mod-utils` module. You can [explore the entire file on GitHub](https://github.com/gethinode/mod-utils/blob/main/data/structures/_arguments.yml). This approach enables reuse and ensures the arguments are consistent across partials and shortcodes. For example, let's consider the `title` argument as defined in `toast.yml`. It includes a single attribute `release` only.

```yml
title:
    release: v1.0.0
```

The `InitArgs.html` function merges the shortcode's type definition with the argument defined in `_arguments.yml`.

```yml
title:
    type:
        - string
        - hstring.RenderedString
        - hstring.HTML
        - template.HTML
    optional: true
    comment: >-
        Title of the element. If the element references a (local) page, the title
        overrides the referenced page's title.
```

Arguments support the following elements:

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `comment` | string |  |  | Documentation about the argument. |
| `default` | string |  |  | Default value when no value is provided. |
| `deprecated` | string |  |  | Points to the version in which the argument was deprecated (it may include the `v` prefix). |
| `editors` | slice |  |  | Per-editor type hints and options. Each item specifies a CMS editor name and an optional type override and editor-specific options for that editor. Currently supported editor names: `cc` (CloudCannon). Takes precedence over type-based inference. |
| `name` | string | yes |  | Argument name. |
| `optional` | string |  |  | Flag to indicate if the argument is optional. |
| `options` | options |  |  | Conditional value requirements, depending on data type. |
| `parent` | select |  |  | Defines if the argument inherits a value from its parent. The value `cascade` indicates the child element should inherit the entire parent's value. When set to `merge`, the parent and child values are merged together separated by a space character. Supported values: [`cascade`, `merge`]. |
| `release` | string |  |  | Points to the version in which the argument was released (it may include the `v` prefix). |
| `type` | string | yes |  | Data type of the argument, either a primitive or complex type. Supported primitive types are `bool`, `int`, `int64`, `float`, `float64`, `string`, `dict`, and `slice`. Complex types can either be a `dict` or a `slice` and should be defined in `data/_types.yml`. |

### Supported primitive types

The following primitive types are supported.

| Primitive      | Description                                                                                                                                                                                                                                                                                                                                                      |
|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| bool           | Boolean, either `true` or `false`. The validation supports both quoted and unquoted values. Maps to the Hugo type `bool`.                                                                                                                                                                                                                                        |
| dict           | A map of key-value pairs, see [collections.Dictionary](https://gohugo.io/functions/collections/dictionary/). Consider using a defined complex type instead.                                                                                                                                                                                                      |
| int, int64     | A whole number, including negative values. Optionally, specify the allowed value range using `options.min` and `options.max`. Maps to the Hugo type `int`.                                                                                                                                                                                                       |
| float, float64 | A fractional number, including negative values. Optionally, specify the allowed value range using `options.min` and `options.max`. Maps to the Hugo type `float64`.                                                                                                                                                                                              |
| path           | Path to a local file or directory. By convention, paths that start with `/` are relative to the repository root. When used as source argument, the base directory may be mapped to one of Hugo's mount folders (e.g. `assets`, `data`, `content`, `static`). Windows paths are mapped to Unix-style paths using forward slashes. Maps to the Hugo type `string`. |
| select         | A single string value from a set of options. Specify the allowed values in `options.values`. Maps to the Hugo type `string`.                                                                                                                                                                                                                                     |
| slice          | An ordered list of values, see [collections.Dictionary](https://gohugo.io/functions/collections/slice/). Consider using a defined complex type instead.                                                                                                                                                                                                          |
| string         | Free format plain text. Maps to the Hugo type `string`.                                                                                                                                                                                                                                                                                                          |

### Supported complex types

Any provided type not matching a primitive is considered a complex type. Type confirmation is tested with `printf "%T"`. For example, to validate if the page context is of the correct type, use `*hugolib.pageState`.
