Executing JavaScript code from an external HTML file

My goal is to create and utilize native web components by defining them as HTML files containing markup, CSS, and Javascript all bundled together in one file, similar to how Vue handles .vue files. These components would be fetched from an external components.html file and loaded onto a page using fetch().

While I have successfully loaded the HTML and CSS, I am facing an issue with the Javascript not being executed by the browser. Vue requires a build step to 'compile' .vue files, which means there is no live loading of .vue files. However, I aim to achieve live loading. Is this idea unconventional?

Most native web component frameworks define their components entirely in Javascript, but I prefer a more declarative approach using HTML without template literal definitions. My objective is to incorporate methods and potentially data into custom elements upon instantiation. Using eval() doesn't seem like a feasible option, correct?

Although having the Javascript initially inactive prevents global scope pollution, I struggle with reading it and injecting it into the custom element class. How can I make it work so that the script within the component, such as the methods object, functions properly?

components.html

<template id="my-dog">
    <style>
        .fur {color: brown}
    </style>
    
    <img src="dog.gif" onclick="speak">
    <p class="fur">This is a dog</p>
    
    <script>
        methods = {
            speak(){alert("Woof!");}
        }
    </script>
</template>

template creation script

//(skipping the part where I fetch components.html)
//(and inject them into the page)
//(and then iterate over each template until...)
templates.forEach(x=>{createTemplate(x)}) //ie. createTemplate('my-dog')

function createTemplate(elementName){
    
    /*various code*/

    let elemClass =  class extends HTMLElement {
        constructor() {
            super(); 
                
            this.attachShadow({mode: 'open'})
                .appendChild(templateContent.cloneNode(true));

        }
    }
    // THIS WORKS! But how can I do the same for speak() function
    // and other functions and variables defined in the template?
    elemClass.prototype['test'] = ()=>{console.log("This is a test")}

    customElements.define(elementName, elemClass);
}

Answer №1

How to Load External HTML/CSS

If you're looking to load external HTML and CSS into your website, check out this helpful blog post on Dev.To by Danny Engelman: https://dev.to/dannyengelman/load-file-web-component-add-external-content-to-the-dom-1nd

Dealing with External Scripts

If you're working with scripts inside templates, it's important to note that they run in the global scope once cloned to the DOM. This can cause issues with frameworks like Angular, which remove script content from templates.

A workaround for Vanilla JavaScript is to use an <img> element with an onerror attribute that triggers code within the element's scope.

To see how scopes are affected when executing Custom Elements, consider the following example:

<my-element id=ONE></my-element>
<my-element id=TWO></my-element>

For a more detailed demonstration, including injecting scripts, you can visit the playground link here: https://jsfiddle.net/CustomElementsExamples/g134yp7v/

Understanding Scope

Ensure you understand scope when working with scripts, as variables can clash due to running in the global scope. It's best practice to avoid creating global variables and use unique function names like "GlobalFunction" to prevent conflicts.

Further Insights:

  • The same template script runs for each connected <my-element>.

  • An alternative to using an <img> element is to utilize

    <style onload="...">
    for a less invasive approach.

Answer №2

After exploring different methods, I have found a solution that works by utilizing an iframe instead of AJAX as outlined here. This approach allows the code within <script> tags to remain active. The code is pulled into the page using an iframe and then the iframe is removed. As long as the code remains enclosed in a <template> tag, it does not interfere with the global scope until it is instantiated within a custom element.

While questions about managing global scope still linger, for now, I am using predetermined global variables defined within templates. In the connectedCallback, I check for these variables, transfer their information to the custom element if they exist, and then clear them for the next connected element. There may be a more efficient way to handle this.

You can view a working example here.

An ideal scenario would involve reading scripts in templates before usage, storing their code in a universal location referenced by each custom element, and avoiding polluting the global namespace during processing.

Improved Approach

During my quest to avoid global variables, I stumbled upon a far superior method detailed here. Recognizing the need to utilize modules, I faced the challenge of importing a module from the same page. Fortunately, solving this issue rendered the previous technique obsolete, though combining the iframe strategy with this one could enhance performance. With just a few additional lines within my createTemplate function, placed before the class definition, I was able to achieve success.

const mod
const templateScript = templateContent.querySelector('script')?.textContent
if (templateScript) {
    const blob = new Blob([templateScript], {type: 'application/javascript'})
    import(URL.createObjectURL(blob)).then( r =>
         mod = r
    )
}

This snippet retrieves the script element from a template, converts it to a blob, and imports it as an external module. Subsequently, I can store the module as needed, such as within the constructor of custom elements. Victory!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

"Pressing the 'back' button on your browser takes

Is there a way to navigate back to the main page by clicking on an image? After selecting First, Picture1 will be shown. How can I go back to the selection page for further choices? <a href="picture1.jpg"> <h3>First</h3></a> <a ...

Setting up Next Js to display images from external domains

Encountering an issue with my next.config.js file while working on a project with Next.js in TypeScript. This project involves using ThreeJs, @react-three/fiber, and @react-three/drei libraries. Additionally, I need to include images from a specific public ...

After props have been passed, the ReactJS ComponentWillMount() function is triggered

Can anyone explain why the child component is only rendered once, even though I pass props to it every time I click a button? Whenever I click a button that passes props to the child, the ComponentWillMount() method of the child component doesn't tri ...

Can we modify the styling of elements in Angular based on an object property?

I have a class named Task with the following properties: export class Task { name: string; state: number; } Within my component.ts file, I have an array of objects consisting of instances of the Task class (tasks). When displaying them in the tem ...

Unusual actions exhibited by the es6 object spread functionality

Check out this interesting example that showcases the power of object spread in JavaScript: module.exports = (err, req, res, next) => { err.statusCode = err.statusCode || 500; err.status = err.status || 'error'; if (process.e ...

Managing Concurrent Execution in Java with Synchronized Cron Job and AJAX calls

Every 5 minutes, a cron job runs to fetch messages from a service and store them in a database. On a JSP page, an AJAX request is executed every 1 minute to check the database for new messages based on time. The issue arises when these two processes over ...

Building an easy-to-use jQuery task list

I'm currently working on a basic to-do list in Javascript, but I've encountered an issue. When I check the checkbox, the style of the adjacent text doesn't change as expected. Instead, it's the heading text that is affected by the chang ...

Navigating through JSON data to retrieve specific values and executing repetitive actions

When the form is submitted, I am making an AJAX request to PHP code and this is the response I receive. var data = { "empty":{ "game_sais_no":"Season cannot contain empty value", "game_sc_no":"Category cannot contain empty value", ...

Understanding the functionality of sessions in CodeIgniter when using AJAX

I am experiencing an issue with the Session in my code. It only works when the page is refreshed. I have a controller called ravi and the view named myview. Here is the code snippet: <?php class Ravi extends CI_Controller { public funct ...

Illumination causes surfaces to transform

In my simple scene, I have a ground and an interesting light source. However, when the light hits certain meshes, it creates some strange effects. The shadows are being cast correctly, but the other meshes affected by the light are showing unusual results. ...

Is there a way to dynamically incorporate line numbers into Google Code Prettify?

Having some trouble with formatting code and inserting/removing line numbers dynamically. The line numbers appear on the first page load, but disappear after clicking run. They don't show at all on my website. I want to allow users to click a button a ...

Why is it that when I return a JSONResult from an overridden JSON method it doesn't function, but a ContentResult does

Recently I encountered an unusual situation while attempting to override the Json method of a Controller class in order to utilize JSON.net contract resolver. Strangely, everything works as expected when I return an object of ContentResult and cast it to A ...

Disable toolbar focus in TinyMCE

Is there a method to prevent the appearance of the white square known as "Focus to toolbar" in TinyMCE? Clarification: Upon pressing Alt+F10 in TinyMCE, a white square is outlined around a button on the toolbar. This allows navigation among toolbar butto ...

"Adding an Image within a Textbox Using Ext JS: A Step-by-Step

How can I add a search image inside a textbox created with the following code? I have created a table with a footer, and within one of the textboxes in the table, I want to include a search button that will call another function when clicked. However, desp ...

mvc and ajax - failing to access model attributes

I'm encountering an issue where the inputs in my beginform are not being auto-posted successfully. Their values do not reach the model or controller and remain null (breakpoints are never hit). What could possibly be causing this? @model project.Mo ...

Unable to render properly after saving to Firebase

Currently, I am working on developing an express app that creates a Google map using geo coordinates extracted from photos. My goal is to utilize Firebase for storing data related to the images. While my code is functioning properly, I encountered an issue ...

React app experiencing issues with onClick button methods not functioning as expected

Here is the React code for a sample homepage. My intention was to have the function run and update the page when the buttons are clicked. However, instead of updating the page, it keeps showing alerts. I have confirmed that the fetch function is pulling da ...

Is it necessary to use JS/JQ to trigger PHP form data?

Can PHP files/functions be executed without reloading the page? It can be quite disruptive when developing a chat app and every time you send a message, the entire page refreshes. I attempted to use AJAX but it didn't work. Is it not possible to send ...

Activate dark mode automatically in material-ui

According to the official documentation: The documentation mentions that a dark mode theme will be automatically generated and reflected in the UI, but I am encountering issues with it. Dependencies: "@emotion/styled": "^11.0.0", ...

Issue with Angular 2: scrolling event not triggering

Just starting out with Angular 2 and I'm trying to implement infinite scrolling for fetching data using REST API calls. Initially, I make a call like this to get the first set of 50 records: localhost:8080/myapp/records=items&offset=0&limit=5 ...