Extend htmlSerializer to add additional params

I am trying to extend the render of the htmlSerializer so I can add additional dynamic params/props to the rendered HTML.

I am using Nuxt/Vue.

Can you help?

What additional info would you like? Please could you give us some examples?

I am using the <prismic-rich-text /> Vue component to render Ordered Lists (via Vue.js).\

  1. I used this rather than $prismic.asText() because I want the content writers to have control of text hightlighting, e.g. ‘bold,’ text labels, etc…
  2. And I didn’t want to rewrite the prismic logic to do this myself.
  3. For Ordered Lists, however, because Prismic doesn’t allow for nested and/or nested, mixed lists, the start position for the Ordered Lists is a problem. Prismic provides (a not-super-great) solution for nested and mixed lists https://user-guides.prismic.io/en/articles/2721186-nested-lists-using-slices.
  4. So, what I would like to do is an additional prop of “startNumber,” or something like to the PrismicRichText component.
  5. And because any data attributes I add to the component are only added to the wrapper, not the ol element, I am finding workarounds.
  6. Here is the file, from the node module:
import { RichText } from 'prismic-dom'

export default {
  name: 'PrismicRichText',
  functional: true,
  props: {
    field: {
      type: Array,
      required: true,
    },
    htmlSerializer: {
      type: Function,
      required: false,
    },
    wrapper: {
      type: String,
      required: false,
      default: 'div',
    }
  },
  render(h, {
    props, data, children, parent
  }) {
    const { field, htmlSerializer, wrapper } = props

    const innerHTML = RichText.asHtml(
      field,
      parent.$prismic.linkResolver,
      htmlSerializer || parent.$prismic.htmlSerializer
    )

    return h(
      wrapper,
      {
        ...data,
        domProps: {
          innerHTML
        },
      },
    )
  }
}

Does that make sense?

There are some workarounds, but I’d prefer to be able to extend Prismic here so that I can add additional props. I think that would be cleaner and more maintainable. I also I can imagine later we’d need to add some additional props.

If I understand correctly, you’ll want to add a html serialiser function. You’re currently defaulting to the prismic one, which is what generates the HTML.

You can see more info in the docs here (I’ve just linked the JS version - the logic is the same).

So you could have something like this:

function htmlSerialiser (type, element, content, children) {
  const serialiser = {
    'group-o-list-item': `<ol>${children.join('')}</ol>`,
    'o-list-item': `<li>${children.join('')}</li>`,
  }

  return serialiser[type] || null
}

Now you can add attributes to the <ol> and <li> elements as necessary.

I create this as a method, and use this.htmlSerialiser, but yours is a functional component so it might not work that way and you’d have to pass it in as a prop.

Thank you for the response.
I was able to create another htmlSerializer, following your above pattern and the one outlined here: https://prismic.io/docs/vuejs/beyond-the-api/html-serializer

Unfortunately, because the htmlSerializer, and how it is used, is defined in the node module, as I copy and pasted above, I don’t have access to this function to add additional params, props, etc… Meaning, all I can deal with are the (type, element, content, children) params. I would like to add another, dynamic one. Thus, the need to be able to extend this component.

Another way of saying this:
I would like to define this htmlSerializer in one of two ways:

  1. Add additional params when the serializer is defined on the component:
    anotherHtmlSerializer(
      type,
      element,
      content,
      children,
      additionalParams,
    ) {
      if (type === Elements.list) {
        return `<ul>${children.join('')}</ul>`;
      }

      if (type === Elements.oList) {
        const startNum = params.startNum;
        const olEl = `<ol start=${startNum}>${children.join('')}</ol>`;

        return olEl;
      }

      // Return null to stick with the default behavior for everything else
      return null;
    },

And I would call it like so:

      <prismic-rich-text
        :field="list.list"
        :htmlSerializer="
          anotherHtmlSerializer((params = { startNum: list.start_number }))
        "
      />
  1. Or be able to add an additional prop field to the component like so:
      <prismic-rich-text
        :field="list.list"
        :startNumber="list.start_number"
      />

Unfortunately, the props are defined on the htmlSerializer and I would need to extend it somehow.

In both cases I am unable to add any additional, dynamic params.

I’d work like this. Create this file here in your components, and import this instead of using the default <prismic-rich-text /> element.

I’ve added in an additional params prop, and created a small html serialiser from your <ol> specification.

Note that you don’t have to use the Elements.oList, there’s nothing special about it - it just returns the string group-o-list-item.

It is a bit spaghettified at the moment, you might get on better removing the functional directive and rendering template tags.

import { RichText } from 'prismic-dom'

export default {
  name: 'ExtendedRichText',
  functional: true,
  props: {
    field: {
      type: Array,
      required: true,
    },
    htmlSerializer: {
      type: Function,
      required: false,
    },
    wrapper: {
      type: String,
      required: false,
      default: 'div',
    },
    params: {
      type: Object,
      required: false,
      default () {
        return {}
      },
    },
  },
  render(h, {
    props, data, children, parent
  }) {
    const { field, htmlSerializer, wrapper, params } = props

    function defaultSerializer (type, element, content, children) {
      if (type === 'group-o-list-item') {
        const startNum = params.startNum
        return `<ol start=${startNum}>${children.join('')}</ol>`
      } else {
        return null
      } 
    }

    const innerHTML = RichText.asHtml(
      field,
      parent.$prismic.linkResolver,
      htmlSerializer || defaultSerializer
    )

    return h(
      wrapper,
      {
        ...data,
        domProps: {
          innerHTML
        },
      },
    )
  }
}

@tgalpin, you can extend the functionality of the HTML Serializer by making your HTML Serializer a closure. That is, make it a function that returns an HTML Serializer function. This will allow you to pass whatever you need to your HTML Serializer. In this case you can pass your starting number.

Then you could do something like this:

const anotherHtmlSerializer = (startingNumber) => {
  return function(type, element, content, children) {
    if (type === Elements.oList) {
      return `<ol start=${startingNumber}>${children.join('')}</ol>`;
    }
    return null;
  }
}

Does this make sense? Give this a try and let me know if you manage to get it working!

That was it!
Thank you Levi. I don’t know why I didn’t think of it. :smiley: