0

I am making a web based chat app like whatsapp web. When clicking on an user then I want to render user.ejs file inside dashbord.ejs.

I am able to load the html and css from user.ejs but js is not working.

I don't use any frontend framework.

I think problem might be with script tag of dashbord.ejs Which is already loaded thats why user.ejs js is unable to load.

How can i fix this problem?

I have tried linking js externally in user.ejs according to other solution but it still not working.

In this section, which is part of dashboard.ejs, the rendered HTML of user.ejs will be inserted.

<div class="webPageCont"> </div>

internal javascript of user.ejs

const connectBtn = document.querySelectorAll('.connect-btn');

for (let i = 0; i < connectBtn.length; i++) {
    connectBtn[i].addEventListener('click', async () => {
        console.log("hello");
        const response = await fetch('/dashbord/loadAllUsers/sentReq', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ sentTo: document.querySelectorAll('.username')[i].value })
        })

    })
}

user.ejs

<% for(let i=0; i<user.length ; i++) { %>
    <div class="user">
        <img src="<%= user[i].profile%>" alt="">
        <span>
            <%= user[i].name%>
        </span>
        <input type="hidden" value="<%= user[i]._id%>" class="username">
    </div>
    <button class="connect-btn">connect</button>
<% } %>

When I am clicking on button then js is not working.

Backend code for rendering

loadAllUsers = async (req, res) => {
    try {
        const user = await users.find({ _id: { $ne: req.user } }).select('-password');
        const loginUser = await users.findById(req.user).select('-password');

        ejs.renderFile(path.resolve(__dirname, '../views/user.ejs'), { user: user, loginUser: loginUser }, function (err, str) {
            if (!err) { res.send(str); }
            else { console.log(err) }
        });
    } catch (err) {
        console.log(err);
        res.status(500).send({ message: 'internal server error.pls try again later.' })
    }
}
2
  • Can you add snippets from your code that highlight the problematic behaviour? Or even better would be a minmal reproducable example. Also how do you render user.ejs? With a request to the server with server-side rendering or on the client side? Commented Aug 22, 2024 at 8:56
  • What happens to the rendered html of user.js after being sent to the client? Are you adding it to the page as document.querySelector('.webPageCont').innerHTML = renderedUser? Commented Aug 22, 2024 at 12:42

2 Answers 2

0

I have looked up and tested different approaches and will list them here, you can then decide what works best for you.

The Problem

But first of all, why does it not work?
I'm assuming you add the HTML string either with container.innerHTML = string or container.insertAdjacentHTML(string). The problem here, is that HTML inserted with innerHTML will not execute scripts.

As W3C describes (source):

Note: script elements inserted using innerHTML do not execute when they are inserted.

And this also goes for insertAdjacentHTML() as that function uses innerHTML.

But there are ways to execute javascript this way though, which is considered a cross-site scripting attack.

MDN writes (source):

However, there are ways to execute JavaScript without using elements, so there is still a security risk whenever you use innerHTML to set strings over which you have no control. For example:

const name = "<img src='x' onerror='alert(1)'>";
el.innerHTML = name; // shows the alert

So it is generally recommended to use textContent instead of innerHTML to prevent security risks, unless you 100% know the HTML is not malicious or can be sanitized beforehand.

Solutions

1. Appending DocumentFragments (source)

This is probably one of the best and straight forward approaches, since you can simply take the whole string, in contrast to other solutions, and insert it with 2 simple lines of code:

const fragment = document.createRange().createContextualFragment(string);
document.querySelector('.webPageCont').appendchild(fragment);

I'm not sure what the differences to innerHTML are, as they both utilize the HTMLparser. But I can assume that the one innerHTML uses has a limitation on parsing scripts. Also the fourth step of the fragment parsing algorithm that createContextualFragment() utilizes says:

  1. Append each node in new children to fragment (in tree order)

which might also play a role in this.


2. Seperately insert script element

This approach is very simple. Put the javascript into a seperate file (e.g. user.js) and manually add a script with the file as src. So it would look something like this:

// get the HTML string by fetch or other means and add to page
document.querySelector('.webPageCont').insertAdjacentHTML('beforeend', HTMLstring_without_script);
// create script element with javascript as source
let script_el = document.createElement('script');
script_el.src = "/user.js"; // adjust depending on file hierachy
// either append to the HEAD
document.querySelector('head').appendChild(script_el);
// or anywhere in the BODY
document.body.appendChild(script_el);

If interested, one can read more on dynamic script loading here.


3. Extract script tags from string

This approach, just like the one before, is pretty simple. When receiving the HTML string on the client-side, extract any script tags from the string and insert them as individual element:

while(/<script>((?:.*?\n*\s*)+?)<\/script>/.test(text)) {
  let reg_match = text.match(/<script>((?:.*?\n*\s*)+?)<\/script>/);
  let script = reg_match[1];
  text = text.replace(reg_match[1], '');
  let s = document.createElement('script');
  s.textContent = script;
  document.querySelector('head').appendChild(s);
}

document.querySelector('.container-content').insertAdjacentHTML('beforeend', text);

But, depending on how the javascript is written, you might not want to load the scripts before the HTML. Then you can just safe each string representing a script tag in an array and add them all after adding the HTML.


4. Script replacer (source)

You could say that this approach is the more advanced version of the one before. You define a method that recursively goes through all the children of a provided element and replaces all not-parsed scripts with functional ones. The method is defined as:

function nodeScriptReplace(node) {
        if ( nodeScriptIs(node) === true ) {
                node.parentNode.replaceChild( nodeScriptClone(node) , node );
        }
        else {
                var i = -1, children = node.childNodes;
                while ( ++i < children.length ) {
                      nodeScriptReplace( children[i] );
                }
        }

        return node;
}
function nodeScriptClone(node){
        var script  = document.createElement("script");
        script.text = node.innerHTML;

        var i = -1, attrs = node.attributes, attr;
        while ( ++i < attrs.length ) {                                    
              script.setAttribute( (attr = attrs[i]).name, attr.value );
        }
        return script;
}

function nodeScriptIs(node) {
        return node.tagName === 'SCRIPT';
}

Which then can be utlized as:

document.querySelector('.webPageCont').insertAdjacentHTML('beforeend', string);
nodeScriptReplace(document.querySelector('.webPageCont'));
Sign up to request clarification or add additional context in comments.

1 Comment

thank you @GreenSaiko i tried 2nd method and it works.
-1
<% for(let i = 0; i < user.length; i++) { %>
    <div class="user">
        <img src="<%= user[i].profile %>" alt="">
        <span>
            <%= user[i].name %>
        </span>
        <input type="hidden" value="<%= user[i]._id %>" class="username">
        <button class="connect-btn">connect</button>
    </div>
<% } %>

can u try with this ?Ensure that each EJS tag (<% %>) is correctly opened and closed.

3 Comments

no it is not working @KRAKENito
What is this trying to solve? OP has problems with dynamically added javascript not being evaluated and executed.
when connect button is clicked it will fetch an endpoint by sending some details to backend then that detail will be saved in db. i am trying to make friend request handling part like facebook or linkedin one user will send request and other will accept it. @GreenSaiko

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.