How do you parse inline href links in Ember Data/JSON string?

The glossary-app I’m working on contains several definitions that link to other definitions. It’s easy to {{#each}} over arrays and string together links to other “See Also” definitions, but I’m struggling with how to work with href/anchor links within definitions.

For example, the original definition for cellulosic polymer looks like this:

Yet in my application it looks like this:

I suspect the answer has something to do with creating a computed property to either construct a DOM node from the HTML using something like didInsertElement or possibly using a RegEx search to grab the hrefs.

This is the closest approximation I’ve found to an answer, but it doesn’t quite get me there.

Here is my terms/definitions template:

<div class="w-700 ml-5 mr-20">
  <p class="text-blue-darkest text-xxl mb-21">{{model.term}}</p>
  {{!-- definition flex --}}
  <div class="flex">

    {{!-- definition --}}
    {{#each (take 1 model.definitions) as |definition|}}

      {{!-- definition div: includes image --}}
      <div class="inline-flex bg-grey-lighter pt-10 pr-0 pb-8 pl-8">

        {{!-- definition flex-1 --}}
        <div class="w-61.2 md:w-full">
          <p class="text-xs font-bold">{{definition.speech_type}} {{definition.category}}</p>
          <p class="text-xs leading-normal pt-24 pr-6 pb-10">
            {{definition.definition}}
          </p>

          {{#if definition.synonyms}}
            <div class="pb-10">
              <span class="text-xs font-bold leading-normal">Synonyms:</span>
              {{#each definition.synonyms as |syn|}}
                <a class="text-xs text-blue-light" href={{syn.link}}>{{syn.title}}</a>{{#unless (eq syn definition.synonyms.lastObject)}},{{/unless}}
              {{/each}}
            </div>
          {{/if}}


          {{#if definition.alternate_forms}}
            <div class="pb-10">
              <span class="text-xs font-bold">Alternate Form:</span> <span class="text-xs">{{definition.alternate_forms}}</span>
            </div>
          {{/if}}

          {{#if definition.see.length}}
            <div class="pb-10">
              <span class="text-xs font-bold">See:</span>
              {{#each definition.see as |see|}}
                {{#if see.title}}
                  <a class="text-xs text-blue-light leading-normal" href={{see.link}}>{{see.title}}</a>{{#unless (eq see definition.see.lastObject)}},{{/unless}}
                {{/if}}
              {{/each}}
            </div>
          {{/if}}

          {{#if definition.more_details}}
            <div class="pb-10">
              <span class="text-xs font-bold">More Details:</span>
              <ul class="text-xs">
                {{#each definition.more_details as |more_details|}}
                  <li>
                    <a class="text-xs text-blue-light leading-normal" href={{more_details.link}}>
                      {{more_details.title}}
                    </a>
                  </li>
                {{/each}}
              </ul>
            </div>
          {{/if}}
        </div>

        {{!-- definition image: part of defintion div --}}
        {{!-- image flex-1 --}}
        {{#if definition.image}}
          <div class="w-38.8 md:w-full">
            <div class="bg-white border p-10 mt--1 mr-8 ml-2">
              <img class="p-10" src={{definition.image.src}} alt="">
              <a {{action (mut showImage) true}} href="" class="no-underline mx-auto">
                <p class="pl-10 bg-grey-light text-center rounded text-sm">
                  {{svg-jar "search-plus" class="w-10 h-10"}} view full-size
                </p>
              </a>
              <figcaption class="p-15">
                <span class="text-xs leading-normal">{{definition.image.caption}}</span>
              </figcaption>
            </div>
          </div>
        {{/if}}
      </div>

      {{#if showImage}}
        {{#modal-dialog
          onClose=(action (mut showImage) false)
          clickOutsideToClose=true
          target=targetElement
          animatable=true
          translucentOverlay=true
          attachment="middle center"
          targetAttachment="middle center"
          constraints=constraints
          }}
          <div class="p-8 bg-white w-full max-w-md m-auto flex-col flex">
            <img class="p-10" src={{definition.image.src}} alt="">
            <figcaption class="p-15">
              <span class="text-xs leading-normal">{{definitnion.image.caption}}</span>
            </figcaption>
            <a {{action (mut showImage) false}} href="">
              <span class="absolute pin-t pin-b pin-r p-4">
                <svg class="h-28 w-28 text-grey hover:text-grey-darkest" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                  <title>Close</title>
                  <path d="M14.348 14.849a1.2 1.2 0 0 1-1.697 0L10 11.819l-2.651 3.029a1.2 1.2 0 1 1-1.697-1.697l2.758-3.15-2.759-3.152a1.2 1.2 0 1 1 1.697-1.697L10 8.183l2.651-3.031a1.2 1.2 0 1 1 1.697 1.697l-2.758 3.152 2.758 3.15a1.2 1.2 0 0 1 0 1.698z" />
                </svg>
              </span>
            </a>
          </div>
        {{/modal-dialog}}
      {{/if}}
    {{/each}}
  </div>
</div>

<div class="flex">
  {{#each (drop 1 model.definitions) as |definition|}}
    <div class="w-full pt-10 pr-0 pb-8 pl-8">
      <div class="w-61.2 md:w-full">
        <p class="text-xs font-bold">{{definition.speech_type}} {{definition.category}}</p>
        <p class="text-xs leading-normal pt-24 pr-6 pb-10">{{{definition.definition}}}</p>

        {{#if definition.alternate_forms}}
          <div class="pb-10">
            <span class="text-xs font-bold">Alternate Form:</span> <span class="text-xs">{{definition.alternate_forms}}</span>
          </div>
        {{/if}}

        {{#if definition.see.length}}
          <div class="pb-10">
            <span class="text-xs font-bold">See:</span> {{#each definition.see as |see|}} {{#if see.title}} <a class="text-xs text-blue-light leading-normal" href={{see.link}}>{{see.title}}</a>{{#unless (eq see definition.see.lastObject)}},{{/unless}}{{/if}}{{/each}}
          </div>
        {{/if}}
      </div>
    </div>
  {{/each}}
</div>

Here is what the cellulosic polymer example looks like this in JSON:

    {
      "id": "cellulosic-polymer",
      "url": "https://www.glossary.oilfield.slb.com/en/Terms/c/cellulosic_polymer.aspx",
      "term": "cellulosic polymer",
      "letter": "c",
      "definitions": [
        {
          "speech_type": "1. n.",
          "category": "[Drilling Fluids]",
          "definition": "<div class=\"definition-text\">\n A drilling-fluid additive used primarily for\n <a href=\"/fluid-loss_control\">\n  fluid-loss control\n </a>\n , manufactured by reacting natural cellulose with monochloroacetic\n <a href=\"/en/Terms/a/acid.aspx\">\n  acid\n </a>\n and\n <a href=\"/en/Terms/s/sodium_hydroxide.aspx\">\n  sodium hydroxide\n </a>\n [NaOH] to form\n <a href=\"/en/Terms/c/cmc.aspx\">\n  CMC\n </a>\n sodium\n <a href=\"/en/Terms/s/salt.aspx\">\n  salt\n </a>\n . Up to 20 wt % of CMC may be NaCl, a by-product of manufacture, but purified grades of CMC contain only small amounts of NaCl. To make CMC, OH groups on the glucose rings of cellulose are ether-linked to carboxymethyl (-OCH2-COO-) groups. (Note the negative charge.) Each glucose ring has three OH groups capable of reaction, degree-of-substitution = 3. Degree of substitution determines water\n <a href=\"/en/Terms/s/solubility.aspx\">\n  solubility\n </a>\n and negativity of the\n <a href=\"/en/Terms/p/polymer.aspx\">\n  polymer\n </a>\n , which influences a CMC's effectiveness as a\n <a href=\"/en/Terms/m/mud_additive.aspx\">\n  mud additive\n </a>\n . Drilling grade CMCs used in muds typically have degree-of-substitution around 0.80 to 0.96. Carboxymethylcellulose is commonly supplied either as low-viscosity (\"\n <a href=\"/en/Terms/c/cmc-lo_vis.aspx\">\n  CMC-Lo Vis\n </a>\n \") or high-viscosity (\"\n <a href=\"/en/Terms/c/cmc-hi_vis.aspx\">\n  CMC-Hi Vis\n </a>\n \") grades, both of which have\n <a href=\"/en/Terms/a/api.aspx\">\n  API\n </a>\n specifications. The viscosity depends largely on the molecular weight of the starting cellulose material.\nReference:\nHughes TL, Jones TG and Houwen OW: \"The Chemical Characterization of CMC and Its Relationship to Drilling-Mud Rheology and Fluid Loss,\" SPE Drilling &amp; Completion 8, no. 3 (September 1993): 157-164.\n</div>\n",
          "see": [
            {
              "title": "carboxymethyl starch",
              "link": "https://www.glossary.oilfield.slb.com/en/Terms/c/carboxymethyl_starch.aspx"
            },
            {
              "title": "carboxymethyl hydroxyethylcellulose",
              "link": "https://www.glossary.oilfield.slb.com/en/Terms/c/carboxymethyl_hydroxyethylcellulose.aspx"
            },
            {
              "title": "carboxymethylcellulose",
              "link": "https://www.glossary.oilfield.slb.com/en/Terms/c/carboxymethylcellulose.aspx"
            },
            {
              "title": "emulsion mud",
              "link": "https://www.glossary.oilfield.slb.com/en/Terms/e/emulsion_mud.aspx"
            },
            {
              "title": "gyp mud",
              "link": "https://www.glossary.oilfield.slb.com/en/Terms/g/gyp_mud.aspx"
            },
            {
              "title": "hydroxyethylcellulose",
              "link": "https://www.glossary.oilfield.slb.com/en/Terms/h/hydroxyethylcellulose.aspx"
            },
            {
              "title": "lime mud",
              "link": "https://www.glossary.oilfield.slb.com/en/Terms/l/lime_mud.aspx"
            },
            {
              "title": "polyanionic cellulose",
              "link": "https://www.glossary.oilfield.slb.com/en/Terms/p/polyanionic_cellulose.aspx"
            },
            {
              "title": "potassium mud",
              "link": "https://www.glossary.oilfield.slb.com/en/Terms/p/potassium_mud.aspx"
            },
            {
              "title": "seawater mud",
              "link": "https://www.glossary.oilfield.slb.com/en/Terms/s/seawater_mud.aspx"
            }
          ],
          "more_details": [

          ],
          "synonyms": [

          ],
          "antonyms": [

          ]
        }
      ]
    }

And here is the term.js model:

import DS from "ember-data";

export default DS.Model.extend({
  term: DS.attr("string"),
  definitions: DS.attr()
});

Anyone ever solved this problem before?

I think you’re looking for triple-curlies. Change {{definition.definition}} to {{{definition.definition}}}.

By default Ember is trying to protect you from XSS attacks. So it treats the text you give it literally. Characters like < become &lt;, so that a user-provided string can never alter your DOM structure.

Triple-curlies opts out of this safety. But then it’s up to you to make sure you’re handling the strings safely, so that (for example) one user can’t inject HTML that would allow them to steal another user’s identity.

1 Like

Yeah, I was looking for a solution outside triple curly braces for the security concerns. Since this is read-only I’m gonna go ahead and punt on this one. I’d still really like to know the answer for future reference, but probably wise to keep it moving.

If you are free to choose the format, use something other than HTML, like MobileDoc.

Interesting. Does this help circumvent the security risks?

Yes, it’s a much simpler format than HTML and it’s designed specifically for this use case.

1 Like

Awesome. Appreciate that!!