Work with Rich Text from DDS

So you’ve created your shiny new web application using DDS and everything is really cool, except for the display of rich text. You’ve figured out that there is a multipart MIME object in the JSON delivered by DDS, and it has the HTML in that, but it still looks crappy. It has tags littered throughout and just doesn’t look good.

Well, I think I’ve got the solution for you. If you look at that multipart MIME Object and find the ‘text/html’ entry you’ll notice a ‘contentTransferEncoding’ property that equals ‘quoted-printable’ or maybe ‘base64’.  There’s the cause of your html display issue. You need a decoder for those 2 encoding types.

Now, let’s take a look at the now-richtext element.

<link rel="import" href="../polymer/polymer.html">

<link rel="import" href="../marked-element/marked-element.html">
<link rel="import" href="quoted-printable.html">

<dom-module id="now-richtext">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>
    <marked-element markdown="{{htmlContent}}">
      <div class="markdown-html"></div>
    </marked-element>
  </template>
  <script>
  Polymer({
    is: 'now-richtext',
    properties: {
      htmlContent: String,
      richTextObj: {
        type: Object,
        observer: '_onRichText'
      }
    },
    _onRichText: function(newVal, oldVal) {
      if (newVal) {
        var textObj = newVal.content;
        for (var i = 0; i < textObj.length; i++) {
          var mimeObj = textObj[i];
          if (mimeObj.contentType.indexOf('text/html') > -1) {
            var html = mimeObj.data;
            if (mimeObj.contentTransferEncoding === 'base64') {
              html = atob(mimeObj.data);
            }else if (mimeObj.contentTransferEncoding === 'quoted-printable') {
              html = quotedPrintable.decode(mimeObj.data);
            }
            var removeText = ['<html>','</html>','<body>','</body>','<head>','</head>'];
            for (var j = 0; j < removeText.length; j++) {
              html = html.replace(removeText[j],'');
            }
            this.set('htmlContent', html);
          }
        }
        // Gotta wait for the DOM to be built
        this.async(function() {
          this._updateImages(textObj);
        }.bind(this), 200);
      }
    },
    _updateImages: function(mimeObjContent) {
      for (var j = 0; j < mimeObjContent.length; j++) {
        var mimeObj = mimeObjContent[j];
        if (mimeObj.contentType.indexOf('image/') > -1) {
          var contentDispo = mimeObj.contentDisposition;
          var isInline = contentDispo.indexOf('inline') > -1;
          var elem = null;
          var srcAttrStr = null;
          if (isInline) {
            var imageBase64 = mimeObj.data;
            var imgElemName = 'cid:' + mimeObj.contentID.substring(1, mimeObj.contentID.length - 1);
            var contentType = mimeObj.contentType.split(';')[0];
            elem = this.querySelector('[src="' + imgElemName + '"]');
            if (elem) {
              srcAttrStr = 'data:' + contentType + ';base64,' + imageBase64;
              elem.setAttribute('src', srcAttrStr);
            }
          }else { // Gotta create an img tag for attachments
            var elemArr = this.querySelectorAll('i');
            for (var k = 0; k < elemArr.length; k++) {
              elem = elemArr[k];
              var dispoArr = mimeObj.contentDisposition.split(';');
              var fileNamePart = dispoArr[1].substring(dispoArr[1].indexOf('="') + 2, dispoArr[1].lastIndexOf('"'));
              if (elem.innerHTML.indexOf(fileNamePart) > -1) {
                var parent = elem.parentNode;
                var img = document.createElement('img');
                var contentTypeArr = mimeObj.contentType.split(';');
                srcAttrStr = 'data:' + contentTypeArr[0] + ';base64,' + mimeObj.data;
                img.setAttribute('src', srcAttrStr);
                parent.replaceChild(img, elem);
              }
            }
          }
        }
      }
    }
  });
  </script>
</dom-module>

This is a really simple element, the things of note here (highlighted lines above):

  • Let’s start with line 4: This is the import of quoted-printable. This is a library by Mathias Bynens that provides a quoted-printable decoder.
  • Next up is line 35: This will decode a ‘base64’ string. This will be placed in the ‘src’ attribute of an image tag.
  • Next is line 37: This will decode a ‘quoted-printable’ string. This is what we’ll hand to the marked-element for display.
  • Lastly is lines 39-42: This will remove the html, body and head tags from the markup (doesn’t really hurt to leave them there tho).

Let’s take a look how we’re dealing with images. Look at the ‘_updateImages’ function. We hand this function the content property of the multipart MIME object. It will then loop through all the parts looking for images. Once it finds an image, it’ll find the corresponding DOM element. If the image is an inline image, there will be an img tag present with a src attribute of something like ‘cid:2__=0ABB0AC0DFF9C40F8f9e8a93df93869091@local’. That’s the same name that is in the mimePart so we use that to find the proper img tag. Once we get the proper img tag, we replace it’s src attribute with the base64 from the mimePart’s data property.

If the image is an attachment we need to take a different route tho. Instead of an img tag, an ‘i’ tag will be created with something like ‘(See attached file: RPN_Logo_Stacked-Preferred.png)‘ as it’s innerHTML. We then get the file name from the mimePart and get all the ‘i’ tags and loop through those until we find the proper one. We then create an img element and replace the ‘i’ tag with the new ‘img’ tag whose ‘src’ attribute has been set to the base64 from the mimePart.

To use this element here’s the demo code:

<link rel="import" href="../../polymer/polymer.html">
<link rel="import" href="../../iron-ajax/iron-ajax.html">
<link rel="import" href="../now-richtext.html">

<dom-module id="doc-wrapper">
  <template>
    <style>
      :host {
        display: block;
        box-sizing: border-box;
      }
    </style>
    <iron-ajax
      url="doc.json"
      handle-as="json"
      last-response="{{doc}}"
      auto>
    </iron-ajax>
    <now-richtext rich-text-obj="{{doc.BulletinText}}"></now-richtext>
  </template>
  <script>
Polymer({
  is: 'doc-wrapper',
  properties: {
    doc: Object
  }
});
  </script>
</dom-module>

This element is purely for the demo included with the now-richtext component. But, this will give us something like this:

screen-shot-2016-10-21-at-4-27-01-pm

This is just the rich text field of a document and is meant to only demo the rich text field. But if you would like to check out the demo, here’s a link to the github page and here’s the repo.

Until next time… Happy Coding!

Share This:

2 thoughts on “Work with Rich Text from DDS

  1. Ah, Keith, that’s just what the doctor ordered. I’ve just been battling with exactly that issue, and I was using multipart=false originally, but that wasn’t playing nicely with the embedded images, because the references were made back to the server.

    Sigh. Rich Text and Web. It’s been painful for at least fifteen years, and it’s so difficult to explain to customers that it’s just going to be an ocean of pain.

    Thanks for the article!

Comments are closed.