This page is part of a static HTML representation of TriTarget.org at https://tritarget.org

Using XML for a custom DSL for Ember components

Sukima29th September 2023 at 3:52pm

I ran into a interesting situation where I wanted to make an Ember component flexible but the internal use of the copmponent had more complexity that it wasn't easy to make flexible in the usual ways.

Specifically I had a component (I’ll call it FooBar) that was attempting to work like this:

<div ...attributes>
  Dolor commodi quidem earum quidem tenetur ullam. Eveniet
  nihil pariatur sed quis iure
  <ButtonLink @linkTo="…" @analytics="banner-click">
    libero ab porro eum molestiae accusamus.
  </ButtonLink>
  Dolor accusantium ex consectetur rerum suscipit Tempore
  rerum adipisci tempora velit labore molestias et
  similique.
</div>

First draft of the example component template.

What I needed was to be able to control the text from the consumer. The nieve approach would be to paramertarize each part.

<FooBar
  @href="/foobar"
  @analyticsKey="foo-bar"
  @text1="Sit ipsam reiciendis nam eos quo sint hic Vel
    sapiente ex"
  @text2="numquam quos odit qui"
  @text3=". Sapiente laborum aperiam quas natus sunt
    adipisicing Voluptatibus rerum velit perferendis
    nesciunt odit animi Nobis"
/>

Example ussage of the FooBar component using tightly coupled parameters.

I this approach we’ve tightly coupled the parameters with the internal implementation. This will cause difficulties if that format of the implementatgion changes. Perhaps a new requirements wants to add a @text4 or we need to use this component but want to not have a @text2. Changing things like this is difficult.

Instead if we could just offer the FooBar component one parameter that is flexible enough to express the needs any way we wish.

{{! Notice <link>numquam quos odit qui</link> in @text }}
<FooBar
  @href="/foobar"
  @analyticsKey="foo-bar"
  @text="Sit ipsam reiciendis nam eos quo sint hic Vel
    sapiente ex <link>numquam quos odit qui</link>. Sapiente
    laborum aperiam quas natus sunt adipisicing Voluptatibus
    rerum velit perferendis nesciunt odit animi Nobis"
/>

Example usage of FooBar that is loosly coupled to the implementation.

In this example we can change the @text in anyway we need. Opt-in to a link or opt-out. Mix and match as needed.

How can we implement this instead?

<div ...attributes>
  {{#each this.parts as |part|}}
    {{#if part.isLink}}
      <ButtonLink
        @linkTo={{@href}}
        @analytics={{@analyticsKey}}
      >
        {{part.text}}
      </ButtonLink>
    {{else}}
      {{part.text}}
    {{/if}}
  {{/each}}
</div>

FooBar component template

import Component from '@glimmer/component';

interface Signature {
  Args: {
    href: string;
    analyticsKey: string;
    text: string;
  };
}

export default class FooBar extends Component<Signature> {
  get parts() {
    return splitTextIntoParts(this.args.text);
  }
}

function * splitTextIntoParts(text: string) {
  let parser = new DOMParser();
  let dom = parser.parseFromString(
    `<root>${text}</root>`,
    'application/xml'
  );

  for (let node of dom.documentElement.childNodes) {
    if (node instanceof Text) {
      yield {
        isLink: false,
        text: node.data,
      };
    } else if (node instanceof Element) {
      yield {
        isLink: node.tagName === 'link',
        text: node.textContent,
      };
    }
  }
}

FooBar component logic

Discuss this article