Rich text: HtmlArea

Contents

If you want to work with rich text in Content Studio, HtmlArea is your friend. This rich text input type offers some very powerful editing capabilities and a lot of configuration options. This whole chapter is dedicated to the HtmlArea: how it works and how to use it in a headless fashion.

The tasks and examples in this chapter are based on work done in previous chapters (starting with the sandboxes chapter). If you want to follow along with the examples, make sure you’re all caught up.

Overview

A text input field with a tools ribbon just above.
Figure 1. The HtmlArea input type with the default tools selection

The HtmlArea input type gives you a multi-line input field with rich formatting options, much like what you’re used to from your favorite word processor. It supports text formatting and justification, lists, links, and even lets you insert images.

You can configure the HtmlArea to be exactly what you want by dictating what formatting tools to include or exclude (the include and exclude attributes, respectively), by what heading levels are allowed (allowedHeadings), and by giving it a default value (default).

Additionally, the HtmlArea supports the use of macros, giving you the ability to insert prefabricated rich components into your markup.

Configuration of text tools

We can use the HtmlArea’s config section to specify which rich text controls should be available when editing. Using a combination of the include and exclude tags, we can customize the input’s appearance.

By default, it includes a limited, but useful selection of tools at your disposal. Using <include>*</include> as an option is the same as including the default tools.

The HtmlArea with default configuration
<input type="HtmlArea" name="everything">
  <label>HtmlArea</label>
</input>

If you want to exclude all controls, use <exclude>*</exclude>:

The HtmlArea configured to include nothing
<input type="HtmlArea" name="nothing">
  <label>HtmlArea with exclude: *</label>
  <config>
    <exclude>*</exclude>
  </config>
</input>
An HtmlArea configured to exclude all editing tools.
Figure 2. An HtmlArea exput with <exclude>*</exclude> and a selection of tools.

Finally, if you want to exclude most tools, but not all, you can exclude all and then add back in the ones you want:

The HtmlArea configured to include a selection of controls
<input type="HtmlArea" name="something">
  <label>HtmlArea with some controls</label>
  <config>
    <exclude>*</exclude>
    <include>Format | Bold Italic Underline | JustifyBlock
    JustifyLeft JustifyCenter JustifyRight | HorizontalRule Blockquote</include>
  </config>
</input>
An HtmlArea configured to include a selection of editing tools.
Figure 3. An HtmlArea with a selection of tools and <exclude>*</exclude>

For a full overview of the available text tools and HtmlArea configuration options, visit the HtmlArea input type reference docs. Additionally, you can find a more thorough explanation of the toolbar and its tools in the rich text editor reference documentation.

Task: Animal description

Let’s add a rich-text description field to our animals. The input should be optional and have at most a single occurrence. You can include any editor tools you want, but it must at least have the Link ,Image and Macro tools, as we’ll be using these later. Otherwise, configure it as you want.

Your content type should now look something like this:

Animal with an HtmlArea input type
<?xml version="1.0" encoding="utf-8"?>
<content-type>
  <display-name>Animal</display-name>
  <description>An animal that lives on planet Earth</description>
  <super-type>base:structured</super-type>
  <form>

    <input type="TextLine" name="otherNames">
      <label>Other names</label>
      <help-text>Other names for this species.</help-text>
      <occurrences minimum="0" maximum="0" />
    </input>

    <input name="images" type="ImageSelector">
      <label>Images</label>
      <help-text>Images of the animal</help-text>
      <occurrences minimum="0" maximum="3" />
    </input>

    <input name="description" type="HtmlArea">
      <label>Description</label>
      <help-text>
        Describe the animal. Where its habitats are, what it eats, etc.
      </help-text>
      <config>
        <exclude>*</exclude>
        <include>Format Link Macro Image</include>
      </config>
    </input>

  </form>
</content-type>

With the new content type deployed, you are ready to complete the specific tasks below.

In addition to good old URLs, in the HtmlArea, you may also insert links to other content items, links to download media files, or links to e-mail.

An important feature, is that when creating internal links (content or media links), Enonic will save a references to the target items, similar to how the ContentSelector works. This is useful when looking for usage and dependencies between content items later.

Because links are created using the item’s unique id. It is perfectly safe to move items around in the tree structure, or change it’s path name. The links will not break.

In this task, you’ll create an internal link to another animal.

Create a new animal, "Lynx".

  1. In the Description field, type some text, i.e. "Lynx are cats, just like lions". Select the "lions" text and click "insert link" in the toolbar.

    You can also use ctrl+K to insert the link.
  2. You’ll be greeted by the "Insert link" dialog: .Choose the content tab, and select the previously created "Lion" as the target item

    Screenshot of the insert link dialog.

When done, save the item - but keep the tab open to complete the upcoming tasks

Images

With the image tool enabled, you insert an image anywhere within your document. During the insert process, you may customize the image further.

The following options are available: Caption, alt text, change the image’s justification, and apply a predefined image styles.

Task: Animal pictures

Try out the image controls that Content Studio give you by uploading a picture and seeing what you can do with it.

We looked at editing images in the previous chapter, but when inserting images in the HtmlArea, you also have something called "image styles", which we’ll have a look at now.

Continue editing the "Lynx" content created in the previous task.

  1. Insert an image below the existing text. Use the "insert image" button in the toolbar.

    The rich text toolbar with the image tool circled by a blue circle.
    Figure 4. The toolbar with the image tool highlighted
    You can also use ctrl+L to insert an image.
  2. You’ll be greeted by the "insert image" dialog:

    The insert image dialog, showing the image, an image styles dropdown, caption, and alt text fields
    Figure 5. The HtmlArea insert image dialog. Photo by Kurt Cotoaga 🦁
  3. When accessing the "image styles" dropdown, you’ll find that it’s only got two options and neither of them seem to do much. To make this more exciting, let’s also create a custom image style.

    In your app, add a new file:

    src/main/resources/site/styles.xml
    <styles>
      <image name="square-grayscale">
        <display-name>Grayscale</display-name>
        <aspect-ratio>1:1</aspect-ratio> (1)
        <filter>hsbcolorize(0x000000); grayscale(); border(6, 0x000000)</filter> (2)
      </image>
    </styles>
    1 The aspect ratio controls how the image is displayed. 1:1 makes the image square.
    2 The filters add a grayscale effect and then a border
    The styles reference doc has more information on how you can style your images, so go check it out for a deeper dive.
  4. With this style picked up by XP, you’ll have the "Grayscale" option available in the image styles dropdown. Select it to apply it to the image.

    The insert image dialog showing the results of applying the image styles to the image
    Figure 6. Example image with an the style applied

Macros

Macros enable you to insert custom components within your rich text field. When the content is rendered, the macro is replaced with the actual component implementation.

Similar to content types, macros can define forms that help editors configure them.

Task: Panel Macros

The Panel Macros app comes with a set of pre-defined macros that may look something like this when rendered:

The Panel Macros package in action, showing differently colored panels.
Figure 7. The Panel Macros in action

To use these macros, we first need to install the Panel Macros.

  1. Navigate to the Applications page (via the XP menu).

  2. Press "Install"

  3. Search for and install the "Panel Macros" app.

  4. Edit your site (My First Site). In the applications dropdown, find Panel Macros, and add it.

  5. Save and exit.

Next up, let’s add a macro to the "Lynx" content item.

The rich text toolbar with the macro button circled by a blue circle.
Figure 8. The toolbar with the macro button highlighted
  1. Click "insert macro".

  2. Select one of the panels. If you have no preference, try the "Info panel".

  3. Add a header and some text to it. For instance:

    Lynx macro content
    Header

    Habitats

    Text

    The lynx inhabits high altitude forests with dense cover of shrubs, reeds, and tall grass.

    A form with 'header' and 'description' fields. Used to set macro parameters.
    Figure 9. The insert macro form with habitat data
  4. If configured, you may also get a preview of what the macro will look like.

    The text we specified in the macro preview form inside a light blue box with an icon attached
    Figure 10. The macro preview function
The Macro can be previewed because the Panel Macros includes a standard preview.

For more information on macros, including how to create your very own, visit to the macro reference docs.

Headless

When fetching rich text via the Guillotine API, the most commonly used output is "processed html". This is essentially unstyled, semantic HTML. It’s then up to you to style the content in your client.

Links, images and macros will potentially require additional client-side processing.

The following query returns the "Lynx" content, and the description field al processed HTML.

Fetching rich text via Headless API
{
  guillotine {
    query(query: "displayName = 'Lynx'")
     {
      ... on com_example_myproject_Animal {
        data {
          description {
            processedHtml
          }
        }
      }
    }
  }
}
The query result may look something like this
{
  "data": {
    "guillotine": {
      "query": [
        {
          "data": {
            "description": {
              "processedHtml": "<p>Lynx are cats, just like <a href=\"/admin/site/preview/default/draft/my-first-site/animals/lion\" data-link-ref=\"b5ccb5ff-7458-4dae-8a4c-bc3f5df9fc63\" title=\"The big cat\">lions</a>.</p>\n\n<figure class=\"captioned conteditor-style-grayscale editor-align-justify\"><img alt=\"A yawning lion cub\" src=\"/admin/site/preview/default/draft/_/image/450a1e29-891b-4d5e-977c-719b5a2b9782:ad62be20ff5129561409bcf5b8aae435e980d09a/width-768/lion-cub.jpg\" data-image-ref=\"3c4d6ce4-4536-4d96-a65d-3e0b81f44f27\" style=\"width:100%\" />\n<figcaption>So sleepy 🤗</figcaption>\n</figure>\n\n<p><editor-macro data-macro-name=\"info\" data-macro-ref=\"1c8cbb83-f0e5-45ce-8027-ced88d165a2d\">The lynx inhabits high altitude forests with dense cover of shrubs, reeds, and tall grass.</editor-macro></p>\n"
            }
          }
        }
      ]
    }
  }
}

By unescaping the HTML, you will find the following:

Processed HTML output
<p>
  Lynx are cats, just like <a href="/admin/site/preview/default/draft/my-first-site/animals/lion" data-link-ref="b5ccb5ff-7458-4dae-8a4c-bc3f5df9fc63" title="The big cat">lions</a>.
</p>

<figure class="captioned square-grayscale editor-align-justify">
  <img
    alt="A yawning lion cub"
    src="/admin/site/preview/default/draft/_/image/450a1e29-891b-4d5e-977c-719b5a2b9782:ad62be20ff5129561409bcf5b8aae435e980d09a/block-768-768/lion-cub.jpg?filter=hsbcolorize%280x000000%29%3B+grayscale%28%29%3B+border%286%2C+0x000000%29"
    data-image-ref="3c4d6ce4-4536-4d96-a65d-3e0b81f44f27"
    style="width:100%" />
  <figcaption>So sleepy 🤗</figcaption>
</figure>

<p>
  <editor-macro
    data-macro-name="info"
    data-macro-ref="1c8cbb83-f0e5-45ce-8027-ced88d165a2d">The lynx inhabits high altitude forests with dense cover of shrubs, reeds, and tall grass.
  </editor-macro>
</p>

With the excepton of macro’s this is all valid HTML. Below, you’ll learn more about handling these elements.

Looking specifically at the processed html link output above, the most interesting part is the data-link-ref attribute.

Wondering why the href points to a location within the admin? No worries. Guillotine automatically generates contextual links. When deploying the API to production, you may customize the base url as see fit.

This example is an internal link, you may want access more details about it, for instance to override the URL, optimize accessibility etc. To acccess these details, you need to extend your query with the links field:

Query for link details
{
  guillotine {
    query(query: "displayName = 'Lynx'")
     {
      ... on com_example_myproject_Animal {
        data {
          description {
            processedHtml
            links {
              ref
              uri
              content {
                displayName
                _path
                type
              }
            }
          }
        }
      }
    }
  }
}

By executing this query, you will get some more data. Specifically:

Link query result
...
"links": [
  {
    "ref": "6931439f-6c4a-4f66-9838-cbd7a0726e80",
    "uri": "content://90685b2f-041d-4114-8828-cf3b68501d3c",
    "content": {
      "displayName": "Lion",
      "_path": "/my-first-site/animals/lion",
      "type": "com.example.myproject:animal"
    }
  }]
...

As you can see, you now have access to the full name of the referenced item, as well as it’s type. Use the ref values from the HTML link element to match it with the specific link data - as you may have more than one link.

Cool, right?

Image srcset

In the processed HTML, you can see the <img> element with style attributes. Additionally, by looking more closely at the src value, you will find that the style settings are applied in the image service URL.

..../_/image/<id>:<version>/block-768-768/lion-cub.jpg?filter=hsbcolorize%280x000000%29%3B+grayscale%28%29%3B+border%286%2C+0x000000%29

But wait, there’s more! If you want to optimize how fast images load on various devices, Guillotine can also return the image in multiple different sizes (using the srcset attribute (MDN)). This is achieved by adding an argument to the description field:

Fetching rich text with srcset
{
  guillotine {
    query(query: "displayName = 'Lynx'") {
      ... on com_example_myproject_Animal {
        data {
          description(processHtml: {imageWidths: [200, 500]}) {
            processedHtml
          }
        }
      }
    }
  }
}

This time, you should find the '<img>' element will include srcset with links to a 200w and 500w respectively.

Img tag, now with srcset
<img alt="kurt-cotoaga-huXZH43-qiw-unsplash.jpg"
  src=".../block-768-768/lion-cub.jpg?filter=..."
  data-image-ref="b1cf729f-2191-4f03-bdf9-95c7e9076e5a"
  srcset=".../block-200-200/lion-cub.jpg?filter=... 200w,.../block-500-500/lion-cub.jpg?filter=... 500w"
  style="width:100%" />
URLs about above were shortened for readability

Image details

If you prefer to handle all details related to images yourself, or optimize the markup further, you may access more details about the image - like you did for links.

Get description with image style details
{
  guillotine {
    query(query: "displayName = 'Lynx'") {
      ... on com_example_myproject_Animal {
        data {
          description {
            images {
              style {
                name
                aspectRatio
                filter
              }
            }
          }
        }
      }
    }
  }
}

You can extract the properties that you specified in your styles.xml previously:

Additional result of fetching image styles
...
"images": [
  {
    "style": {
      "name": "editor-style-grayscale",
      "aspectRatio": "1:1",
      "filter": "hsbcolorize(0x000000); grayscale(); border(6, 0x000000)"
    }
  }
]
...

Macros

In the headless context, the client is responsible for rendering of your content. In the case of macros - this gets obvious.

In the processed HTML, each macro is represented by an <editor-macro> element containing a unique reference key. As for links and images, this reference can be used to access detailed data fields for the macro.

Query that fetches all macro data
{
  guillotine {
    query(query: "displayName = 'Lynx'")
     {
      ... on com_example_myproject_Animal {
        data {
          description {
            processedHtml
            macrosAsJson
          }
        }
      }
    }
  }
}
MacrosAsJson response (extract)
...
"macrosAsJson": [
  {
    "ref": "9b983010-9bb3-4b96-8200-92d43f9c2140",
    "name": "info",
    "descriptor": "com.enonic.app.panelmacros:info",
    "config": {
      "info": {
        "body": "The lynx inhabits high altitude forests with dense cover of shrubs, reeds, and tall grass.",
        "header": "Habitats"
      }
    }
  }
]
...

To access specific macros and fields, like for content type may use the full GraphQL schema. Try the following:

If you used a different macro than the "Info" macro, the query must be updated to match the specific macro.
Query that fetches the fields for the info macro
{
  guillotine {
    query(query: "displayName = 'Lynx'")
     {
      ... on com_example_myproject_Animal {
        data {
          description {
            processedHtml
            macros{
              ref
              descriptor
              name
              config {
                info {
                  header
                }
              }
            }
          }
        }
      }
    }
  }
}

This time, you only get the explicit fields you were looking for

Output from the field-based query
...
"macros": [
  {
    "ref": "2f7601da-e586-414e-a387-b2e76731f4bb",
    "descriptor": "com.enonic.app.panelmacros:info",
    "name": "info",
    "config": {
      "info": {
        "header": "Habitats",
      }
    }
  }
]
...

By accessing the macro config, you are able to down drill into every single field, accessing all the details you actually you need.

That’s a wrap for rich text handling!


Contents