JS Prevent multiple functions running simultaneously - javascript

I am trying to make a page which redirect users to different pages after 20s using different buttons or elements and I want to prevent multiple timeouts I mean i want to keep 20s interval between redirecting to any site.
function redirect1(){
window.open('https://youtube.com', '_blank')
}
function redirect2(){
window.open('https://google.com', '_blank')
}
<html>
<p onclick='settimeout(redirect1, 20000)'>Youtube</p>
<p onclick='settimeout(redirect2, 20000)'>Google</p>
</html>

In the code below timeouts[site] = timeouts[site] || makes sure that the button can be clicked only once every 20 seconds
Also, created a single function so as not to have repetitive code
const redirect = (() => {
let timeouts = {};
return (site, delay) => {
timeouts[site] = timeouts[site] || setTimeout(() => {
window.open(site, '_blank');
timeouts[site] = null;
}, delay);
};
})();
<html>
<p onclick="redirect('https://youtube.com', 20000)">Youtube</p>
<p onclick="redirect('https://google.com', 20000)">Google</p>
</html>

Related

Simplify two functions with same functionalities?

I have two functions that do the same thing, but are used differently.
I currently have a popup that is triggered on actions based on device. On desktop, if the mouse leaves the page, it triggers the popup (function called exitUserMessage). On mobile, if the mouse is inactive for 10 seconds+ it triggers the popup (function called inactiveUserMessage). See below:
//display popup for inactive users
const inactiveUserMessage = () => {
//other code was here but deleted bc not relevant
return dismissModal();
};
//display popup for users leaving the page
function exitUserMessage() {
//other code was here but deleted bc not relevant
return dismissModal();
}
Here is where the functions are used, both separately for desktop and mobile:
if (width <= 480) {
// Start the timer on page load
window.addEventListener("load", () => {
timer = setTimeout(inactiveUserMessage, 10000);
});
document.addEventListener("mousemove", debounce(inactiveUserMessage, 10000), timer);
} else {
// Trigger popup for desktop
document.addEventListener("mouseleave", (e) => {
if (Evergage.cashDom("#abandon-cart-message").length === 0) {
return exitUserMessage();
}
});
}
inactiveUserMessage is used in the debounce function that delays it by 10 seconds, then triggers the popup to show.
exitUserMessage is returned when the mouse leaves the page for desktop. My question is both of these functions are doing the same thing above ^.
Any ways to simplify? I am fairly new to JavaScript so looking to learn best practice!
Something worth noting: these are tracked as separate actions in our dataLayer.
Thank you!
You can call the inactiveUserMessage variable like functions. If there are different parts in those functions, add a parameter like isDesktop to inactiveUserMessage and debounce functions . If true, run the relevant desktop code in if statement.
const inactiveUserMessage = (isDesktop) => {
if (isDestop) {
//Desktop
} else {
//Mobile
}
return dismissModal();
};
if (width <= 480) {
// Start the timer on page load
window.addEventListener("load", () => {
timer = setTimeout(inactiveUserMessage, 10000);
});
document.addEventListener("mousemove", debounce(inactiveUserMessage, false, 10000), timer);
} else {
// Trigger popup for desktop
document.addEventListener("mouseleave", (e) => {
if (Evergage.cashDom("#abandon-cart-message").length === 0) {
return inactiveUserMessage(true);
}
});
}

Click event on a button with SetInterval Method

I'm not able to figure out how this can be accomplished. I'm working on the chrome extension and I have a setInterval method which monitors a button on a page and when its clicked, it runs a function. but the problem is, when I click the button, the function runs multiple times and I clearly understand this is because of the Interval function, but I want the setInterval to run always on this page so that I can monitor if the button is clicked or not. Below is my code
$(function(){
var url=window.location.href;
findAction(url);
}
function findAction(url){
setInterval(()=>{
Acceptbtn = $("[slot=primary-content-area")[4].querySelector("sn-inbox");
if(Acceptbtn !== undefined && Acceptbtn !== null){
Acceptbtn.addEventListener('click', myFunction);
}
function myFunction() {
console.log("clicked");
runAction(url)
};
},1000);
}
Is there any way to tackle this situation or have I made something simple, complicated or Is my approach completely wrong.?
Below is the Div which my extension monitors for the Accept button -
And once the Accept button is clicked, this is what happens
The Interval method keeps checking for this button and once found and I click on it, I want the runAction(url) function to execute. With my current code, the function gets executed but multiple times
Only add an event listener once
Also the selector is incorrect if sn-inbox is a class and [slot=primary-content-area] was missing a ]
Here I delegate - if you delegate to the closest static container (here document, because I do not know the closest container in your case), you can ALWAYS find the button when it is there:
const url = window.location.href;
$(document).on("click", "[slot=primary-content-area]", function() {
if ($(this).index() === 3) { // 0 based - so the 4th slot
const $inbox = $(this).find(".sn-inbox");
if ($inbox.length > 0) {
$inbox.html("running");
runAction(url)
}
}
})
Example code to execute the functions:
https://jsfiddle.net/mplungjan/p93cj0Le/
$(function() {
const runAction = url => console.log("running", url);
const url = window.location.href;
$(document).on("click", "[slot=primary-content-area]", function() {
if ($(this).index() === 3) { // 0 based
const $inbox = $(this).find(".sn-inbox");
if ($inbox.length > 0) {
$inbox.html("running");
runAction(url)
}
}
})
// testing the code
setInterval(() => {
$("#container").html(`<div>
<div slot="primary-content-area"></div>
<div slot="primary-content-area"></div>
<div slot="primary-content-area"></div>
<div slot="primary-content-area"><button class="sn-inbox">Click</button></div>
</div>`)
}, 5000)
setTimeout(() => $("#container").html(""), 2000)
})
<div id="container">
</div>

Reduce the impact of third-party code (zendesk)

<script
id="ze-snippet"
src="https://static.zdassets.com/ekr/snippet.js?key=some_zendesk_key"
/>
I'm trying to optimize my web site performance. I've faced a big impact of third-party code to my performance, I think all my bundle has a lower size than zendesk code. How can I load it without impacting on the main thread? Should I use the async or defer tags? Or which approach is better for this case?
This seems to be an issue that tortures so many people without a clear solution.
What I managed to do it to reduce the block time by adding this configuration.
window.zESettings = {
webWidget: {
chat: {
connectOnPageLoad: false
}
}
};
ref https://developer.zendesk.com/embeddables/docs/widget/settings#connectonpageload
ps
I did a performance test to my zendesk helpdesk "domain.zendesk.com" and the results there were even worse
I came across this issue recently and made this hack using a function for loading the zendesk script only when you reached certain point of the doc. I know is kind of dirty but it works:
<script defer type="text/javascript">
(function($, win) {
$.fn.inViewport = function(cb) {
return this.each(function(i,el){
function visPx(){
var H = $(this).height(),
r = el.getBoundingClientRect(), t=r.top, b=r.bottom;
return cb.call(el, Math.max(0, t>0? H-t : (b<H?b:H)));
} visPx();
$(win).on("resize scroll", visPx);
});
};
}(jQuery, window));
$('#trigger_div').inViewport(function(px){
if(px) {
//zopim code
}
});
Starting from this article https://www.newmediacampaigns.com/blog/maintaining-great-site-performanc-using-zendesk-web-widget I have implemented a solution that significantly reduces the load time by at least 3 seconds (in Google Lighthouse).
I have created a fake button in the HTML that will load the Zendesk script and open the widget when clicked. It will also load a localStorage item that will prevent this from happening on subsequent page loads.
⚠️ Warning:
The code relies heavily on how the widget is currently implemented (for example it expects a #launcher and a #webWidget element to appear on the page), so it can break as soon as the original code changes, but at least we will have an improvement in the loading times until they fix it.
Here is the most important part of the code:
HTML Button
<button class="zendesk-button">
<span class="left-icon">
<!-- here you insert the icon -->
</span>
<span class="text">Chat</span>
</button>
JavaScript code
// select the button
const zendeskButton = document.querySelector('.zendesk-button');
// add the script to the page
const loadZendeskScript = () => {
const zenDeskScript = document.createElement("script");
zenDeskScript.id = "ze-snippet";
zenDeskScript.src = "https://static.zdassets.com/ekr/snippet.js?key=HERE_YOU_INSERT_YOUR_KEY";
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0] || document.getElementsByTagName('script')[0].parentNode).insertBefore(zenDeskScript, null);
};
// a poller that waits for a condition and executes a callback
const poller = (comparison, callback, timerStep = 250, maxTime = 5000) => {
// why setTimeout instead of setInterval
// https://stackoverflow.com/questions/8682622/using-setinterval-to-do-simplistic-continuous-polling
let currTime = 0;
const checkCondition = () => {
// `comparison` is a function so the condition is always up-to-date
if (comparison() === true) {
callback();
} else if (currTime <= maxTime) {
currTime += timerStep;
setTimeout(checkCondition, timerStep);
}
};
checkCondition(); // calling the function
};
// load the script and execute a callback if provided
const loadZendeskChat = callback => {
loadZendeskScript();
if (callback) {
callback();
}
};
// this function opens the chat
const openZendeskChat = () => {
poller(
() => {
// check that zendesk-related functions are available
return typeof zE !== 'undefined';
},
() => {
// open the widget
zE('webWidget', 'open');
poller(
() => {
// check that the elements exist and that the opacity is already set to "1"
const launcher = document.querySelector('#launcher');
const webWidget = document.querySelector('#webWidget');
return launcher !== null && webWidget !== null && webWidget.style.opacity === '1';
},
() => {
// hide the fake button
zendeskButton.style.opacity = '0';
// save in localStorage
localStorage.setItem('zd_hasOpened', 'true');
}
);
}
);
};
// zendesk management
if (localStorage.getItem('zd_hasOpened')) {
// load the zendesk widget if we find that it was opened
loadZendeskChat();
} else {
// show the fake button if it's the first time it shows
zendeskButton.style.opacity = '1';
}
// This will run when the .zendesk-button element is clicked
zendeskButton.addEventListener('click', () => {
// add a 'Loading' text to the button, as the widget will take some time to load (between 1 and 2 seconds on my laptop)
zendeskButton.querySelector('.text').innerHTML = 'Loading';
// load the zendesk widget
// open the widget and hide the fake button
loadZendeskChat(openZendeskChat);
});
Regarding styles, I have pretty much copied the style in the original widget, converting ems to pixels, but one part I'd like to highlight is the focus style, because in my opinion it helps telling the user that something is happening.
.zendesk-button:focus {
outline: none;
box-shadow: inset 0 0 0 0.21429rem rgb(255 255 255 / 40%) !important;
}

need to set a timer for in javascript , then clear

I want to create a timer in JavaScript. I see the setTimeout(fn, 100) but unclear how to wrap this so it will clear itself at the end.
I tried doing
var buttonTimer = null;
$scope.backButton = function() {
if(buttonTimer === null){
$history.back();
}
buttonTimer = setTimeout(function(buttonTimer){
buttonTimer = null;
}, 100);
}
The whole point is to prevent the user from hitting this function too quickly.. and ignoring all subsequent clicks within that 100ms window, at the end of the window, clear the timer and resume accepting clicks
Since you are doing angular, I prepared a plnkr for demonstration:
http://plnkr.co/edit/5qrslKpmkglXTvEyYgBr?p=preview
Your code is almost Ok, the only problem is that you start a new timeout on every click. The effect is, that the callback fires multiple times and resets buttonTimer.
So the only change is:
var blocker = null;
$scope.backButton = function() {
if(blocker == null) {
blocker = setTimeout(function(){
blocker = null;
}, 1500);
// DO YOUR STUFF HERE
}
};
You can use throttle from lodash/underscore or Ramdajs.
for example
$scope.backButton=_.throttle(100,function(){/* do something here */});

Javascript: How to ensure that onClick events are only triggered once prior events have completed?

I've written code that colors points on a canvas when the user clicks them. Points are colored by way of a fade-in animation, so it takes a certain amount of time to color a given point. Problems occur if the user clicks a new point while a prior point is still being colored: anomalous colors are produced, fade-in animations continue indefinitely without stopping, etc.
I would like is to make it so that, when the user clicks on a point, any subsequent clicks are ignored until the function coloring that point has completed. Upon completion, the user should be able to click another point to color it (i.e. the onClick coloring functionality should be restored).
In searching for solutions to this problem, I mostly found code designed for events that are only executed once. However, this is not quite what I need. My user should be able to be able to trigger an arbitrary number of coloring events--I just don't want any of these events to ever happen concurrently, as it were. Thanks for any help!
You can use locking, like:
var block = false;
element.addEventListener("click", function () {
if (block === true) {
return;
}
block = true;
doOperation("params", function () {
block = false;
});
});
function doOperation(input, callback) {
setTimeout(callback, 1000);
}
This blocks clicks for about 1 second.
Commonly you'll use promises instead of the callback:
var block_promise = null;
element.addEventListener("click", function () {
if (block_promise === null || block_promise.is_resolved() === false) {
return;
}
block_promise = doOperation("params");
});
function doOperation(input, callback) {
var promise = new Promise();
setTimeout(function () {
promise.resolve();
}, 1000);
return promise.promise();
}
Note that Promises are not (yet) natively supported in JavaScript. Use your favorite library.
If you have a way of setting a flag when painting is occurring, you could try this. Here I set a flag to true when painting starts and false when it is done. In the buttonClick() function, I immediately exit if painting is true.
<!DOCTYPE html>
<html>
<head>
<title>Button Test</title>
</head>
<body>
<script>
var painting = false;
var clicks = 0;
function buttonClick() {
//if painting, immediately exit buttonClick
if (painting)
return;
//display click count whenever we enter here
clicks++;
var obj = document.getElementById("clicks");
obj.value=clicks;
//simulate painting
painting = true;
document.getElementById("status").innerHTML = "painting in progress";
setTimeout(function() { colorPoint()}, 10000 /* 10 seconds */ );
}
function colorPoint() {
//simulated painting
painting = false; //allows button to be clicked again
document.getElementById("status").innerHTML = "painting done";
}
</script>
<button onclick="buttonClick()">Click me</button>
Button clicks: <input type="text" name="clicks" id="clicks">
<div id="status"> </div>
</body>
</html>

Categories