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.' })
}
}
I have looked up and tested different approaches and will list them here, you can then decide what works best for you.
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.
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:
- Append each node in new children to fragment (in tree order)
which might also play a role in this.
script
elementThis 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.
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.
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'));