[SalesForce] Dynamically Render Toast Message With Icon

I wrote a dynamic component to build toast messages based on ApexPages.Message in my answer here, but one aspect of it has been bothering me ever since, and I'm trying to fix it. When the toast is rendered, its icon doesn't display. It doesn't seem to be an error with the markup, since if I inspect the element and modify the source to remove it then add it back in verbatim, the icon then renders. The exact same markup also works fine if rendered on page load.

Here is an MVR:

<apex:page showHeader="false" standardStylesheets="false" sidebar="false"
           applyHtmlTag="false" applyBodyTag="false" docType="html-5.0">
    <html xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" lang="en">
        <head>
            <title>Demo Toast</title>
            <apex:slds />
        </head>
        <body>
            <div class="slds-scope">
                <div id="target" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
                    <div id="messageOnPageLoad" class="slds-notify slds-notify--alert slds-theme--alert-texture slds-theme--warning" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" role="alert">
                        <span class="slds-icon_container">
                            <svg class="slds-icon slds-icon-text-default slds-icon--small slds-m-right--x-small" aria-hidden="true">
                                <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/apexpages/slds/latest/assets/icons/utility-sprite/svg/symbols.svg#info"></use>
                            </svg>
                        </span>
                        <h2><span>Here is a toast message!</span></h2>
                    </div>
                </div>
            </div>
        </body>
    </html>
    <script>
        (function (D, w) {
            "use strict";
            var targetDiv = document.getElementById("target");
            w.buildToast = function (message) {
                var toast = targetDiv.appendChild(D.createElement("div"));
                toast.className = "slds-notify slds-notify--alert slds-theme--alert-texture slds-theme--warning";
                toast.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
                toast.setAttribute("xmlns", "http://www.w3.org/2000/svg");
                toast.setAttribute("role", "alert");

                var icon = toast.appendChild(D.createElement("span"));
                icon.className = "slds-icon_container";

                var svg = icon.appendChild(D.createElement("svg"));
                svg.className = "slds-icon slds-icon-text-default slds-icon--small slds-m-right--x-small";
                svg.setAttribute("aria-hidden", true);

                var use = svg.appendChild(D.createElement("use"));
                use.setAttribute("xlink:href", "/apexpages/slds/latest/assets/icons/utility-sprite/svg/symbols.svg#info");

                toast.appendChild(D.createElement("h2"))
                    .appendChild(D.createElement("span"))
                    .appendChild(D.createTextNode("Here is a toast message!"));
            };
            D.addEventListener("DOMContentLoaded", w.buildToast);
        }(document, window));
    </script>
</apex:page>

Why are my dynamically rendered toast icons not displaying properly?

Toasts

I'm using Chrome 62 on Mac, but I don't think it's worked on any Chrome version I've installed.

Best Answer

This isn't a Lightning problem, but a generic element namespace problem. You have to create the element in the correct "namespace" for this to work. Credit to this question for pointing out the problem.

Basically, the SVG and USE elements need to be created in the SVG namespace using the createElementNS methods. Also, some of the attributes had to be set via setAttributeNS in order for it to work correctly. I may have slightly overkilled, but the version below appears to be identical.

<apex:page showHeader="false" standardStylesheets="false" sidebar="false"
           applyHtmlTag="false" applyBodyTag="false" docType="html-5.0">
    <html lang="en">
        <head>
            <title>Demo Toast</title>
            <apex:slds />
        </head>
        <body>
            <div class="slds-scope">
                <div id="target" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
                    <div id="messageOnPageLoad" class="slds-notify slds-notify--alert slds-theme--alert-texture slds-theme--warning" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" role="alert">
                        <span class="slds-icon_container">
                            <svg class="slds-icon slds-icon-text-default slds-icon--small slds-m-right--x-small" aria-hidden="true">
                                <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/apexpages/slds/latest/assets/icons/utility-sprite/svg/symbols.svg#info"></use>
                            </svg>
                        </span>
                        <h2><span>Here is a toast message!</span></h2>
                    </div>
                </div>
            </div>
        </body>
    </html>
    <script>
        (function (D, w) {
            "use strict";
            var targetDiv = document.getElementById("target");
            w.buildToast = function (message) {
                var toast = targetDiv.appendChild(D.createElement("div"));
                toast.className = "slds-notify slds-notify--alert slds-theme--alert-texture slds-theme--warning";
                toast.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
                toast.setAttribute("xmlns", "http://www.w3.org/2000/svg");
                toast.setAttribute("role", "alert");

                var icon = toast.appendChild(D.createElement("span"));
                icon.className = "slds-icon_container";

                var svg = icon.appendChild(document.createElementNS("http://www.w3.org/2000/svg","svg"));
                svg.setAttribute("class","slds-icon slds-icon-text-default slds-icon--small slds-m-right--x-small");
                svg.setAttribute("aria-hidden", true);

                var use = svg.appendChild(D.createElementNS("http://www.w3.org/2000/svg","use"));
                use.setAttributeNS("http://www.w3.org/1999/xlink", "href", "/apexpages/slds/latest/assets/icons/utility-sprite/svg/symbols.svg#info");

                toast.appendChild(D.createElement("h2"))
                    .appendChild(D.createElement("span"))
                    .appendChild(D.createTextNode("Here is a toast message!"));
            };
            D.addEventListener("DOMContentLoaded", w.buildToast);
        }(document, window));
    </script>
</apex:page>