Why is it only executed once when generating dynamic javascript? - javascript

I'm making a web application based on Express and axios. I'm trying to create a static navigation and load an HTML file on each menu click while dynamically loading the associated script file.
Page Router
...
router.get('/page/home/:location', (req, res) => {
const location = req.params.location;
fs.readFile(path.resolve(__dirname + `../../../views/pages/${location}.ejs`), (error, html) => {
if(error) {
res.status(500).end();
} else {
res.status(200).end(html);
}
});
});
Main(.ejs)
...
<button class="nav-grid-item-menu-button" type="button" onclick="mainSidebarUiMouseclick(this)" value="diary">Diary</button>
...
<div id="content"></div>
Controller
const mainSidebarUiMouseclick = (event) => {
const content = document.getElementById('content');
let script;
// Remove all child
content.innerHTML = '';
axios.get(`${window.location.href}/${event.value}`)
.then((res) => {
content.innerHTML = res.data;
})
.then(() => {
switch(event.value) {
case 'diary':
script = document.createElement('script');
script.setAttribute('src', `/js/pages/ui-${event.value}-controller.mjs`);
script.setAttribute('crossorigin', 'anonymous');
script.setAttribute('type', 'module');
script.defer = true;
script.addEventListener('load', () => {
console.info(`#system, Dynamic script loading complete`);
});
content.appendChild(script);
break;
...
}
})
.catch((error) => { console.error(`#error, ${error}`); });
};
The script is always executed the first time it is clicked without any problem. However, if you return to another menu after moving to another menu, the script is not executed normally. This structure is not available for document.body. The reason is that all existing script files are dynamically erased when the menu is clicked. How should I solve it?

Related

Insert script tag without always refetching the data

I am injecting an iframe, through a script tag in my React component - for the purpose of using it in a chrome extension:
const StickyBar= () => {
const ref = useRef(null);
useEffect(() => {
let refValue;
if (ref.current) {
const script = document.createElement("script");
script.src = "";
ref.current.appendChild(script);
refValue = ref.current;
};
return () => {
if (refValue) {
while (refValue.firstChild) {
refValue.removeChild(refValue.firstChild);
};
};
};
}, [ref,]);
return (
<div className="sticky-bar" >
<div ref={ref} />
</div>
);
};
The problem is that on each page change the data within the iframe refetches and it causes an unpleasant user experience. Is there any way I can always stick the bar on top of each page - without always having to create the script tag?
Thanks a lot !

Force Nuxt to run script tag

I have a script on a page:
<script type="text/javascript">app_id="ID_HERE";distribution_key="dist_6";</script><script type="text/javascript" src="https://loader.knack.com/ID_HERE/dist_6/knack.js"></script><div id="knack-dist_6">Loading...</div>
If I go to the page via a NuxtLink in the navigation the script runs, however if I type the URL to the browser address bar it doesn't.
Is there a way to force the NuxtLink result when the page is accessed directly?
The script tag is coming from a CMS so there isn't a way to hardcode it somewhere nice.
There is a head method (Docs) which will let you to load external scripts but
I can't find the documentation for the script property.
I would dynamically load the script only on the client side like this:
<template>
<div id="knack-dist_6">Loading...</div>
</template>
<script>
export default {
// ...
async created() {
// Do not load the script on the server side
if (!process.client) return
// Function to load the external script
function loadLib(id, distKey) {
const scriptId = 'knack-js'
return new Promise((resolve, reject) => {
// If script already exists than do not load it again
if (document.getElementById(scriptId)) {
resolve()
return
}
const s = document.createElement('script')
s.src = `https://loader.knack.com/${id}/${distKey}/knack.js`
s.id = scriptId
s.type = 'text/javascript'
s.onload = () => {
resolve()
}
s.onerror = (e) => {
reject(e)
}
document.head.appendChild(s)
})
}
try {
if (!window.app_id) {
window.app_id = 'ID_HERE'
window.distribution_key = 'dist_6'
}
await loadLib('ID_HERE', 'dist_6')
} catch (e) {
// Handle script loading error
console.error(e)
}
}
}
</script>

My Buttons are not working after using fetch api along with express.js

I have strange problem with buttons that are requesting for displaying templates on client page.
This is client side code. The main task of entire class is to just enable user to click button, send request and get response with HTML that has been rendered from handlebars template and just paste it in partiuclar place on client side. It works, but only once. After first click and displaying elements, I totally lose any interaction with those buttons. There is no request, and there is no even EventListener for clicking. I get no error. Completely there is no single reaction after clicking.
class Weapons {
constructor() {
this.buttons = document.querySelectorAll('.type')
}
async displayWeapon(path) {
const container = document.querySelector('.shop-container')
await fetch(`weapons/${path}`).then(response => response.json()).then(data => container.innerHTML += data);
}
chooseWeapon() {
this.buttons.forEach(btn => {
btn.addEventListener('click', (e) => {
console.log('click');
let weaponType = e.target.dataset.type
switch (weaponType) {
case 'pistols':
console.log('click');
return this.displayWeapon(weaponType)
case 'rifles':
console.log('click');
return this.displayWeapon(weaponType)
case 'grenades':
console.log('click');
return this.displayWeapon(weaponType)
case 'closerange':
console.log('click');
return this.displayWeapon(weaponType)
case 'rocketlauchner':
console.log('click');
return this.displayWeapon(weaponType)
}
})
})
}
}
document.addEventListener('DOMContentLoaded', function () {
const weapons = new Weapons();
weapons.chooseWeapon();
> When I invoke displayWeapon(path) here it also works, but immidiately
> after displaying html elements clicking on buttons again does not
> initiate any action.
})
Here is app.get function but I doubt it's source of problem.
app.get('/weapons/:id', (req, res) => {
console.log('req');
console.log(req.url);
let type = req.params.id;
res.render(type, function (err, html) {
res.json(html);
})
})
Ok. The answer is actually simple. In fetch function container.innerHTML += data. This line deletes my html with buttons, and the same time it deletes eventListeners. So I need just to modify my html.

Checking inside dynamically modified iframe with Cypress

I have a page that includes a third party script (Xsolla login). This script modifies elements on the page, one of the particular elements being iframe.
First a placeholder element is inserted
Then the iframe is deleted and new iframe is inserted with different dynamically loading content
Both iframes have the same id
How one can detect when the second, replaced, iframe is correctly loaded as Cypress cy.get() grabs the first iframe and then never detects newly changed content within the replaced iframe?
You can use cypress-wait-until plugin and then write a custom check function that inspects deep into the iframe.
/**
* Because Xsolla does dynamic replacement of iframe element, we need to use this hacky wait.
*/
function checkLoadedXsollaIframe(doc: Document): bool {
try {
const iframe = doc.getElementById('XsollaLoginWidgetIframe') as any;
if(!iframe) {
return false;
}
// This element becomes available only when Xsolla has done its magic JS loading
const usernameInput = iframe.contentDocument.querySelector('input[name="email"]');
return usernameInput !== null;
} catch(e) {
return false;
}
}
context('Login', () => {
beforeEach(() => {
cy.visit(frontendURL);
});
it('Should login with valid credentials', () => {
// This injects Xsolla <script> tag and then
// this third party script takes its own course of actions
cy.get('.login-btn').first().click();
cy.waitUntil(() => cy.document().then(doc => checkLoadedXsollaIframe(doc)), {timeout: 5000 });
Below is the snippet that waits for the content inside the iframe to be loaded and HTMLElements be available & no timeouts required.
const iframeElement = (selector) => {
const iframe = cy.get(selector);
return iframe
.should(($iframe) => // Make sure its not blank
expect($iframe.attr('src')).not.to.include('about:blank')
)
.should(($iframe) =>
expect($iframe.attr('src')).not.to.be.empty) // Make sure its not empty
.then(($inner) => {
const iWindow = $inner[0].contentWindow;
return new Promise((resolve) => {
resolve(iWindow);
});
})
.then((iWindow) => {
return new Promise((resolve) => {
iWindow.onload = () => { // Listen to onLoad event
resolve(iWindow.document);
};
});
})
.then((iDoc) => {
return cy.wrap(iDoc.body); // Wrap the element to access Cypress API
});
};
Now access the element inside the iframeDocument
iframeElement('#my-iframe') // Grab the iframe
.find('h2')
.should('have.text', 'My header text'); //Assert iframe header
Note: Don't attempt to access CORS websites. It might fail due to
security reasons

Injecting javascript function in nightmareJS

I was given a javascript function that I need to inject into a page in order to get a list of values that would be used later on. I can call this function directly on the webpage using the Chrome console but I want to replicate what I did in the Chrome console in nightmareJS on the webpage that is currently loaded.
This is the function:
function getList() {
require(['Service/List'],
function (Service)
{
Service.getList
({
onComplete: function (listOfServices)
{
console.log('List:success:' + JSON.stringify(listOfServices));
},
onFailure: function (error)
{
console.log('List:error:' + error);
}
});
});
}
getList();
I've tried injecting the file but I have had no success, I've also tried adding additional code to that function to write the output to a file but I do not think its being called at all.
Here is the nightmareJS
describe('Open login page', () => {
it('Login', done => {
nightmare
.goto('http://loginURL.com')
.wait('input[type="text"]')
.wait('input[type="password"]')
.insert('input[type="text"]', 'username')
.insert('input[type="password"]', 'password')
.click('input[type="submit"]')
.evaluate(function() {
nightmare.inject('js', 'getList.js' )
})
//.inject('js', 'getList.js' )
.then(function() {
console.log('Done');
})
})
})
})
This is the sample output after injecting the javascript file into the page:
List:success:"Test":"https://someURL.com/resource/test","Design":"https://someURL.com/resource/Design"},"NewSpace":"https://someURL.com/resource/NewSpace","Generator":"https://Generator.someURL.com/resource/test","SomethingElse":"https://someURL.com/SomethingElse/test","Connection":"https://someURL.com/Connection/test","WorldWide":"https://someURL.com/resource/WorldWide","Vibes":"https://Vibes.someURL.com/resource/test","GoogleSearch":"https://someURL.com/resource/GoogleSearch",
I want to be able to get that output from calling the javascript file on the page and save it to a file so that I can use it later to call other services in that list.
You can read the local javascript files that needs to be injected:
var fileData = [];
fileData.push(fs.readFileSync(path.resolve('../getList.js'), 'utf-8'));
It can be loaded into head section of the page via code:
browser.win
.evaluate(function(fileData) {
var elem = null;
for(var ii=0;ii<fileData.length; ii++ ) {
elem = document.createElement('script');
if (elem) {
elem.setAttribute('type', 'text/javascript'); //elem.src = localjs;
//console.log(fileData[ii]);
elem.innerHTML = fileData[ii];
document.head.appendChild(elem);
}
}
console.log("Testing loaded scripts");
console.log(getList());
return "Injected Scripts";
}, fileData)
.then(function(result) {
console.log(result);
}).catch(function(error) {
console.log("Load Error: ", error);
});

Categories

Resources