admin管理员组文章数量:1417104
I'm trying to build a vanilla js single page application (using node.js and express) but I've ran into an issue with executing .js files that I just can't find any solution to, after hours of scouring the web and stackoverflow...
Below is the fairly simple structure with this problem. I have a static header for navigation in my index.html with <a> elements triggering the SPA routing and a container div for the SPA views.
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<script src='../scripts/index.js' type='module' defer></script>
</head>
<body>
<nav>
<a href='/testA' class='route'>Test A</a>
<a href='/testB' class='route'>Test B</a>
</nav>
<div id='views'></div>
</body>
</html>
Exemplary TestA.html
<!DOCTYPE html>
<html lang="en">
<head>
<script src='../scripts/TestA.js' type='module' defer></script>
</head>
<body>
<p> View: Test A </p>
</body>
</html>
TestA.js
const obj = {name: 'A', val: '000'}
console.log(obj)
index.js
const nav = document.querySelectorAll('.route')
for (const element of nav){
element.addEventListener('click', (event) => {
event.preventDefault()
history.pushState(null, '', element.href)
spaRouting()
})
}
const container = document.getElementById('views')
function spaRouting(){
const path = window.location.pathname
const html = '...' //fetch request grabbing HTML file from node server
container.innerHTML = html
const oldScripts = container.getElementsByTagName('script')
for (const oldScript of oldScripts){
const newScript = document.createElement('script')
for(const attribute of oldScript.attributes){newScript.setAttribute(attribute.name, attribute.value)}
newScript.addEventListener('load', () => console.log('script was loaded'))
oldScript.replaceWith(newScript)
}
}
window.addEventListener('popstate', spaRouting())
The routing itself works perfectly fine, as does the HTML injection. Jumping back and forth between Test A and Test B I can see the HTML content change. Replacing the scripts with newly created ones also works great and Test??.js is executing and logging to console.
However, it does so only exactly one time for each navigation element / view. If I click a view a second time the HTML content still updates successfully but no scripts are executed. I do get the "script was loaded" logged to console from the eventListener of the newly created script element but the content of the .js files does not get executed (no object logged to console).
For the life of me I can't figure out why this works but does so only once for every view and not any subsequent time.
I tried:
- appending the new script tag and then removing the old one instead of using replaceWith/replaceChild
- creating the new script without copying attributes from the old script but instead defining them manually
- giving each script tag a unique ID so that every time a script tag is added it has a new ID
- appending the new script to head or body of index.html instead of the container div and then removing the (inactive) old script inside the container
What I can not do is preload all script files inside index.html because in the actual project many of them reference DOM elements which don't exist until the corresponding view is loaded (because the project is coming from a multi page application structure).
I really hope someone can explain this behavior to me and knows a way how to fix it because I'm at a loss.
I'm trying to build a vanilla js single page application (using node.js and express) but I've ran into an issue with executing .js files that I just can't find any solution to, after hours of scouring the web and stackoverflow...
Below is the fairly simple structure with this problem. I have a static header for navigation in my index.html with <a> elements triggering the SPA routing and a container div for the SPA views.
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<script src='../scripts/index.js' type='module' defer></script>
</head>
<body>
<nav>
<a href='/testA' class='route'>Test A</a>
<a href='/testB' class='route'>Test B</a>
</nav>
<div id='views'></div>
</body>
</html>
Exemplary TestA.html
<!DOCTYPE html>
<html lang="en">
<head>
<script src='../scripts/TestA.js' type='module' defer></script>
</head>
<body>
<p> View: Test A </p>
</body>
</html>
TestA.js
const obj = {name: 'A', val: '000'}
console.log(obj)
index.js
const nav = document.querySelectorAll('.route')
for (const element of nav){
element.addEventListener('click', (event) => {
event.preventDefault()
history.pushState(null, '', element.href)
spaRouting()
})
}
const container = document.getElementById('views')
function spaRouting(){
const path = window.location.pathname
const html = '...' //fetch request grabbing HTML file from node server
container.innerHTML = html
const oldScripts = container.getElementsByTagName('script')
for (const oldScript of oldScripts){
const newScript = document.createElement('script')
for(const attribute of oldScript.attributes){newScript.setAttribute(attribute.name, attribute.value)}
newScript.addEventListener('load', () => console.log('script was loaded'))
oldScript.replaceWith(newScript)
}
}
window.addEventListener('popstate', spaRouting())
The routing itself works perfectly fine, as does the HTML injection. Jumping back and forth between Test A and Test B I can see the HTML content change. Replacing the scripts with newly created ones also works great and Test??.js is executing and logging to console.
However, it does so only exactly one time for each navigation element / view. If I click a view a second time the HTML content still updates successfully but no scripts are executed. I do get the "script was loaded" logged to console from the eventListener of the newly created script element but the content of the .js files does not get executed (no object logged to console).
For the life of me I can't figure out why this works but does so only once for every view and not any subsequent time.
I tried:
- appending the new script tag and then removing the old one instead of using replaceWith/replaceChild
- creating the new script without copying attributes from the old script but instead defining them manually
- giving each script tag a unique ID so that every time a script tag is added it has a new ID
- appending the new script to head or body of index.html instead of the container div and then removing the (inactive) old script inside the container
What I can not do is preload all script files inside index.html because in the actual project many of them reference DOM elements which don't exist until the corresponding view is loaded (because the project is coming from a multi page application structure).
I really hope someone can explain this behavior to me and knows a way how to fix it because I'm at a loss.
Share Improve this question edited Feb 3 at 8:50 DarkBee 15.5k8 gold badges72 silver badges118 bronze badges asked Feb 3 at 0:29 Unfrosted9025Unfrosted9025 11 silver badge4 bronze badges 5 |1 Answer
Reset to default -1Okay so I actually found the reason (kind of) and more importantly, the solution!
I had the suspicion that it had something to do with the script being cached and that's why it isn't executed again after the first time. However, when I tried the "disable cache" setting in my browser's developer's tools it didn't help.
However, it IS a caching issue, just on a different level. Basically, what appears to happen is that after, let's say, TestA.js is executed, the browser remembers that file name and the next time it is invoked by a new script element it recognizes it as having been already executed earlier so it is ignored (if anyone has a deeper understanding of the mechanics underneath all this feel free to comment and elaborate on this).
The solution is to add a parameter with a unique value to the file source on each iteration, like so (using Date.now() as unique value):
function spaRouting(){
const path = window.location.pathname
const fileID = Date.now() // <==== HERE
const html = '...' //fetch request grabbing HTML file from node server
container.innerHTML = html
const oldScripts = container.getElementsByTagName('script')
for (const oldScript of oldScripts){
const newScript = document.createElement('script')
for(const attribute of oldScript.attributes){newScript.setAttribute(attribute.name, attribute.value)}
newScript.src = `${newScript.src}?fileID=${fileID}` // <==== AND HERE
newScript.addEventListener('load', () => console.log('script was loaded'))
oldScript.replaceWith(newScript)
}
}
With this it now works like a charm. :)
本文标签: javascriptInserted scripts execute only onceStack Overflow
版权声明:本文标题:javascript - Inserted scripts execute only once - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1745253206a2649945.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
window.addEventListener('popstate', spaRouting())
? Shouldn't that bewindow.addEventListener('popstate', spaRouting)
? – Kostas Minaidis Commented Feb 3 at 0:44()
you are immediately calling the function, not attaching it as an event listener. Compare with()
vs without()
– VLAZ Commented Feb 3 at 7:44