Next.js Vanilla JS script won't run on deployment - javascript

I'm using Next.js to build a simple static site. The only vanilla JS script I have is for a mobile menu - to toggle it open and add a class to the body to prevent scrolling:
if (process.browser) {
document.addEventListener('DOMContentLoaded', function() {
let mainNav = document.querySelector('#menu')
let navBarToggle = document.querySelector('#toggle')
let noScroll = document.querySelector('body')
navBarToggle.addEventListener('click', function () {
mainNav.classList.toggle('active')
navBarToggle.classList.toggle('close')
noScroll.classList.toggle('lock-scroll')
})
})
}
The if process.browser statement is something I had to add to get it to work on localhost, which works but only on the first load, e.g. when I first run next dev. After I navigate to another page it won't load, I guess because the page isn't being fully re-rendered, which the script needs or something?
Then when I deploy the site the toggle isn't working at all, even on first load.
Anyway, any help in getting this working would be much much appreciated!

process.browser has been deprecated, use type of window
if (typeof window !== "undefined") {
document.addEventListener('DOMContentLoaded', function() {
let mainNav = document.querySelector('#menu')
let navBarToggle = document.querySelector('#toggle')
let noScroll = document.querySelector('body')
navBarToggle.addEventListener('click', function () {
mainNav.classList.toggle('active')
navBarToggle.classList.toggle('close')
noScroll.classList.toggle('lock-scroll')
})
})
}
if that solution doesn"t work, you can use dynamic import

Related

Cypress: How to handle closing of window confirm triggered from an iframe

I need help on how to close a confirmation window which was triggered from an iframe. I am trying the code below, but this still can't close the confirm window.
cy.get('iframe[id="ParentMainContent_Frame"]').then(($iframe) => {
const $body = $iframe.contents().find('body')
const $win = $iframe[0].contentWindow
cy.stub($win,'confirm').as('windowConfirm')
cy.wrap($body)
.find('#ParentMainContent_MainContentMaster_ctl00_PlaceOrderButton').click().should(function () {
expect(this.windowConfirm).to.be.calledWith('Thank you for your Order!')
})
})
The button triggering this window is shown above:
Hoping someone could take a look and help.
Using cypress-iframe gives you more checks that the iframe loading has finished before you start testing it's contents.
The downside is this library's methods yield body but not the iframe window, though I think you can use body.ownerDocument.window to get it.
cy.enter('iframe[id="ParentMainContent_Frame"]').then(getBody => {
const body = getBody();
const win = body.ownerDocument.window;
cy.spy(win,'confirm').as('windowConfirm'); // spy not stub
body.find('#ParentMainContent_MainContentMaster_ctl00_PlaceOrderButton')
.click()
.should(function () {
expect(this.windowConfirm).to.be.calledWith('Thank you for your Order!')
})
})
})

Window onload event from an external JS script is never triggered in React PWA

I am working on a React based PWA. For using an external service I have to add a JS script at runtime. Currently I'm adding it (based on the instructions provided by the service) to the document like so:
function initializeScript() {
const script = document.createElement('script');
script.id = 'source-script';
script.src = 'https://some.url.com';
document.body.appendChild(script);
}
Adding the script is successful as there is the following code available in the browser.
if (document.querySelector('#dom-source')) {
console.info("Binding to the window.onload event");
window.onload = function() {
console.info("The parent window is loaded");
// some code
};
}
The first console.info is being executed. However - and this is my main issue - the window.onload function isn't being executed after that. I guess that's due to the fact that I'm using React and it somehow never triggers the onload event.
Is there a workaround or am I missing something? I can't alter the source code of the added JS code as it's from an external provider. Also I have to add it dynamically after some initialization work of the #dom-source element.
Any help would be much appreciated! Thanks!
UPDATE:
After some trial and error and based on this How do i use external script that i add to react JS? answer I came up with the following semi-working solution:
let A: any;
const scriptLoaded = useCallback(() => {
console.info('script loaded');
// #ts-ignore
A = new A() // A is a class in the external JS file
A.someFunctionOfScript();
}, [])
useEffect(() => {
const script = document.createElement('script');
script.id = 'source-script';
script.src = 'some.url.com'
script.onload = () => scriptLoaded();
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}, [token]);
Instead of waiting for the window.onload event, I'm executing the scriptLoaded function. Its code is just pasted from the external files window.onload function.
So this works perfectly up until unmounting or route switching. I'm getting exceptions from the external file. It basically tries to execute the code but the elements like #dom-source are no longer available. Well, at least the script is being executed now.
Anyone got an idea of how to stop the script execution and remove all references to it when unmounting?

Uncaught TypeError: Cannot read property 'addEventListener' of null when i try to run my code in chrome

So when I try to run my html file in the browser chrome logs that error, but in visual studio code it doesn't register any problems. How can i fix this and ensure it doesn't happen in the future? My js code:
const navbarBtn = document.querySelector("navbar__btn");
const navbarLinks = document.querySelector("navbar__links");
navbarBtn.addEventListener("click", function () {
let value = navbarLinks.classList.contains("navbar__collapse");
if (value) {
navbarLinks.classList.remove("navbar__collapse");
} else {
navbarLinks.classList.add("navbar__collapse");
}
});
There are two things potentially wrong with your code.
Script executed before html is ready
You need to make sure your JS is loaded after the required html is loaded. Without going in detail two good methods for that are:
loading the script at the bottom of the html file
<script src="main.js"></script>
</body>
Using defer on your script
<script defer src="./main.js"></script>
</head>
Your elements are not loaded correctly
In your example you are trying to access your DOM elements like this:
const navbarBtn = document.querySelector("navbar__btn");
However, this is looking for a custom tag <navbar__btn>. I believe it is more likely that you are looking for a tag with the class of "navbar__btn" so you have to add a point to refer to a class.
const navbarBtn = document.querySelector(".navbar__btn");
For a jquery code to run, it must always be enclosed within $(), which you have missed in navbarBtn.addEventListener("click", function () {
Try this:
const navbarBtn = document.querySelector("navbar__btn");
const navbarLinks = document.querySelector("navbar__links");
$(navbarBtn).addEventListener("click", function () {
let value = navbarLinks.classList.contains("navbar__collapse");
if (value) {
navbarLinks.classList.remove("navbar__collapse");
} else {
navbarLinks.classList.add("navbar__collapse");
}
});
Also, you really don't need to the addEventListener function as Jquery automatically detects if you click on a button.
This code should also work:
$(navbarBtn).on('click,function(){
// your logic here
});
The problem is, it is not finding the node that you are trying to attach the event to. So it has to be associated to the working of the document.querySelector() method.
You might want to send an id or class of the node, but this is not the right way to pass values of id and class.
You should rewrite you code as
// If you are passing id
const navbarBtn = document.querySelector("#navbar__btn");
const navbarLinks = document.querySelector("#navbar__links");
// If you are passing class
const navbarBtn = document.querySelector(".navbar__btn");
const navbarLinks = document.querySelector(".navbar__links");
Remember you will get the first element in case you have passed class as argument.

Function works when I put it in an onclick but doesn't work when I try to use in on page-load (JavaScript)

I am building a Microsoft VSTS extension using HTML JavaScript.
I need to access this function VSS.getWebContext() which gives you all information of the project page where you are currently running your extension.
let webContext = VSS.getWebContext();
let projectName = webContext.project.name + "_kpi_test"
# prints out fine
console.log('projname', projectName);
When I put this into an onclick function of a button, it works fine and prints the information that I need.
But when I just use
$(window).ready(function(){
// your code
});
and
$(document).ready(function(){
// your code
});
and
window.onload = codeAddress; //my function here
to use the VSS.getWebContext() to retrieve the information when the page loads, it keeps giving me that VSS.getWebContext() is undefined.
If it works on onClick but not on page-load, what could be the problem?
I am not sure what exactly VSS needs, but since it is working in onClick it is probably just that the page has not actually completely loaded yet in your document and window ready calls.
You could try:
$(window).bind("load", function() {
// code here
});
There is an VSS.init and VSS.ready function, see use the SDK from vss-web-extension-sdk GitHub for more information.
In Angular & TypeScript:
// VARIABLES VSS
extensionInitializationOptions: IExtensionInitializationOptions = { applyTheme: false, usePlatformScripts: false, usePlatformStyles: false };
async ngOnInit(): Promise<void> {
if (environment.production) {
// VSS INIT
VSS.init(this.extensionInitializationOptions);
// VSS READY
VSS.ready(async () => {
await this.init();
});
} else await this.init();
}
environment.production for local debugging.

Sammy.JS without automatic runRoute on app.run?

Is there a way to make it so Sammy.JS does not automatically call runRoute when you call app.run()?
My code currently initializes Sammy on the first load, but does not want it to actually call any sammy routes until the user actually clicks a link.
You could try passing in a non-operational route to the run method. It might ignore any route in the hash in that case.
Otherwise, you could set a listener on the document root to listen for clicks in the document and run the application then. But this solution seems "less clean."
(assuming jQuery)
$(function () {
var app = Sammy();
$("a").live(function () {
if (!app.isRunning()) {
app.run();
}
});
});
Question is old but I used the following solution that I find a little cleaner.
The page is loaded normally and Sammy doesn't call the current route when using .run().
http://sammyjs.org/docs/api/0.7.4/Sammy.Application.methods.before
var appIsRunning = false;
var app = Sammy(function() {
this.before('.*', function() {
if (this.path == document.location.pathname && appIsRunning == false) {
return false;
}
});
// The routes...
...
}
// start the application
app.run();
if (app.isRunning()) { appIsRunning = true; }

Categories

Resources