How to stamp out template in self contained custom elements with vanilla js?

595 views Asked by At

I have a custom component that just shows a tarot card. Before the custom element I have defined a template.
In my wc's connectedCallback I attached the template itself to the shadowroot and then stamped it out by cloning it there in the shadowroot as well. I did this for 2 reasons:

  1. I want my wc component to be a self contained module; therefore I want to define my template in the same place as my custom element.
  2. It seems to be the only way to stamp out my template to make it usable without sticking it in an owner document.

    var tmpl = `
        <template id="tmpl">
        <h1 class="tarot-title"><slot name="title">NEED TITLE</slot>
        </h1>
        <img src="${this.imageurl}" alt="">
        <p><slot name="subtitle">NEED A SUBTITLE</slot></p>
    </template>`;
    
    
    
    class BdTarot extends HTMLElement {
    
        ...constructor etc...
    
        connectedCallback() {
            this._shadowRoot.innerHTML = tmpl;
            var _tmpl = this._shadowRoot.querySelector('#tmpl');
            this._shadowRoot.appendChild(_tmpl.content.cloneNode(true));
    
        }
    
    }
    
    customElements.define('bd-tarot', BdTarot);
    

The problem this has created is every tarot card component I use on my page has the same template is a child, all with the same id. Since they're in the shadowroot, does it matter? Smells funny though...

My goal is simply trying to understand how the web components specs all fit together. My question is, is there a better way to do this that keeps my component code together and doesn't refer back to an owner doc? Is the template spec mostly incompatible with custom elements since html imports are not being adopted by most browser vendors?

3

There are 3 answers

0
Supersharp On BEST ANSWER

In a nutshell: if you use template literals then you shouldn't use <template> element.

You don't need to duplicate the template to keep the custom element and template codes together.

You can simply enclose your code in a self-executed function to be sure that the tmpl variable won't be overridden.

(function () {

var tmpl = `
    <h1 class="tarot-title"><slot name="title">NEED TITLE</slot></h1>
    <img src="${this.imageurl}" alt="">
    <p><slot name="subtitle">NEED A SUBTITLE</slot></p>`;


class BdTarot extends HTMLElement {
    constructor() {
        super()      
        this.attachShadow( { mode: 'open' } ) 
             .innerHTML = tmpl;
    }
}

customElements.define('bd-tarot', BdTarot);

})()
<bd-tarot>
<span slot="title">Queen</span>
</bd-tarot>

If you want to keep a local copy of the template, you can copy it in an instance variable (this.tmpl).

0
Fezal halai On

Try to used below code which is add custom stamp annotate in header. Make sure that custom stamp image size is 100*100 pixel. Otherwise stamp image are blur or zoom out.

const customStampTool = new Tools.RubberStampCreateTool(documentViewer);
const customImage = '../stamp.png';

// Register tool
instance.UI.registerTool({
    toolName: stampToolName,
    toolObject: customStampTool,
    showPresets: true,
    dataElement: 'customStampToolButton',
    buttonImage: 'icon-tool-stamp-fill',
    buttonName: 'customStampToolButton',
    tooltip: 'Custom Stamp',
});

// Add tool button in header
instance.UI.setHeaderItems(header => {
    header
        .getHeader('toolbarGroup-Annotate')
        .get('highlightToolGroupButton')
        .insertBefore({
            type: 'toolButton',
            toolName: stampToolName,
            onClick: async () => {
                const imageSrc = customImage;
                const canvasHeight = 140;
                const canvasWidth = 160;
                const imageSize = 80;
                const stampAnnot = new Annotations.StampAnnotation();
                stampAnnot.PageNumber = 1;
                stampAnnot.X = 50;
                stampAnnot.Y = 50;
                stampAnnot.Width = canvasWidth;
                stampAnnot.Height = canvasHeight;

                // create a canvas in memory to draw your text to
                const canvas = document.createElement('canvas');
                canvas.width = canvasWidth;
                canvas.height = canvasHeight;
                const context = canvas.getContext('2d');

                // Create an image object. This is not attached to the DOM and is not part of the page.
                var image = new Image();

                // When the image has loaded, draw it to the canvas and add the annot to annotation manager
                image.onload = function () {
                    context.imageSmoothingEnabled = false;
                    context.drawImage(image, 40, 0, imageSize, imageSize);
                    context.font = "7pt Calibri";
                    context.fillText("Revise Name", 0, imageSize + 25);
                    context.fillText(userName, 0, imageSize + 30);

                    var currentdate = new Date();
                    var datetime = currentdate.getDate() + "/"
                        + (currentdate.getMonth() + 1) + "/"
                        + currentdate.getFullYear() + " "
                        + currentdate.getHours() + ":"
                        + currentdate.getMinutes() + ":"
                        + currentdate.getSeconds();
                    context.fillText(datetime, 0, imageSize + 35);

                    // convert your canvas to a data URL
                    const dataURL = canvas.toDataURL();

                    // put your data URL here
                    stampAnnot.ImageData = dataURL;

                    annotationManager.addAnnotation(stampAnnot);
                    annotationManager.redrawAnnotation(stampAnnot);
                }

                // set width and height of the image and set the source.
                image.width = imageSize;
                image.height = imageSize;
                image.src = imageSrc;
            }
        });
});
0
GullerYA On

While I agree with @Supersharp's statement that template literal and template should not be used together, I think there is a better way to manage the component's HTML template, somewhat of mix between your approaches.

In essence, instead of dealing with string or template literal, one should define a template once and inject it from there on upon each component's instance creation.

const template = document.createElement('template');
template.innerHTML = `
    <h1 class="tarot-title">
        <slot name="title">NEED TITLE</slot>
    </h1>
    <img src="${this.imageurl}" alt="">
    <p>
        <slot name="subtitle">NEED A SUBTITLE</slot>
    </p>
`;

customElements.define('bd-tarot', class extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' }) 
            .appendChild(template.content.cloneNode(true));
    }
})();

I've wrote on the similar point here, mentioning also my own library wrapping up most of the above boilerplate into few APIs.