Is there a possible way to synchronize events in javascript?
My situation is following: I have a input form with many fields, each of them has a onchange event registered. there is also a button to open a popup for some other/special things to do in there.
My requirement is, that the onchange event(s) are finished before I can open the popup.
Any ideas how I can achieve that without using setTimeout?
EDIT: further explanation of requirements:
To clarify my situation I try to detail what I'm doing.
I got a form with some input items (order entry matrix form, e.g. article, serial#, count). Every time user changes data in one of the fields an ajax call is triggered by an onchange event to validate the user input and read additional data (e.g. presetting/formating one of the other fields). These ajax calls are heavy and cost time, so I have to avoid duplicate validations.
There is also a button which opens a popup which gives the user an other form to change data he entered before line by line, so it is absolutely necessary that all validations are done before this popup is opened.
At the moment I try to synchronize the onchange events and the popup opening using setTimeout (popup isn't opened before all validations are done), which causes problems at my customers site because these popups are trapped by the popup blocker.
So I need to open my popups without getting stopped by some popup blocker (IE 6/7/8).
Because of my matrix-form I just can't validate all input items before opening the popup, I need to validate only those which have been changed and are not validated yet (should be at most 1).
It sounds like you are doing form validation, with an automatic popup when the form has been fully completed. To do that, you write a single validation function in javascript that checks every field on the form. You can fire this function from each of your OnChange events, and have the function open the popup when the entire form successfully validates.
Consider checking out jQuery, when you have a little free time.
http://jquery.com/
you can set up a little callback to your onchange events to insure that all of your validation occurs before the popup.
function onChange(callback)
{
// Do validation
// Call the callback
callback();
}
function showPopup()
{
// Show the popup
}
Then on your onchange call just call
onChange(showPopup);
If you set a global variable and use setTimeout to check if it is set properly. Depending on how complex the situation is you can either use a boolean, two booleans, a number that increments, or even an object. Personally I would proly use an object as that way I know which one hasn't fired yet. something like var isDone = {username: 0, password: 0, password2: 0};
Let assume by input fields you are meaning only text inputs and not any checkboxes or comboxes( I'm guessing you are trying to make a sort of auto-completion).
My advice is to use onkeyup and onkeydown.
var keypressed = false;
function onkeydown( )
{
keypressed = true;
}
function onkeyup( )
{
keypressed = false;
setTimeout( function()
{
if (!keypressed)
show_popup();
else
setTimeout( this.calee,1000)
}, 1000 );
}
Set flags (variables) for each group of validations.
Initiate the flag at 0.
Set the flag to 1, when
validation is complete for the group.
When the user pops the button, if all
flags are 1, popup the window.
The callback that Jon mentioned would solve the problem of "what do you do if they are not yet all validated?"
EDIT: Added after clarification:
Have you considered adding the popup button, via DOM methods (easy) (or innerHTML, if you like), after everything is validated? That way, there is no option shown before its time. :D
Also, do you test if a popup is blocked? If it is, you could branch to either a notice to the user that their blocker is blocking the editor; or to loading your editor into an iframe automatically; or to loading the editor to the main page via DOM methods (appending documentFragment, etc.).
Some blockers give users the option to block even popups generated from clicking on links (which were traditionally off limits to blockers). I would think you would benefit from some kind of a backup method, or at least a warning system in place regardless.
HTH
i don't think i have completely understood your question, but here are some thoughts on solving problems you may have :)
first, i'd deactivate the popup-opening button when the ajax call is sent. then, when the requested data arrives and all validation is done, activate it again. you can do this with a counter: increment it for every sent request, decrement it as soon data arrives and validation is completed. activate the popup opening button when data arrives and the counter is zero. this prevents the user from clicking the popup opening button while there are still validation requests pending.
you can use the same technique for the input fields themselves: lock the input fields that await validation by setting them to readonly, unlock them when everything is done.
to prevent problems when the user changes form values while the ajax call hasn't yet returned, you have several options:
use a timer for sending the request: everytime an onchange event is fired, wait x seconds before sending the request. if another onchange event happens before the ajax request is sent, reset that timer. this way, several onchange events withing a certain timeframe trigger just 1 ajax request. this helps reducing load.
you can calculate and store checksums for every position, so if an onchange event is fired, calculate the checksums again and compare them. this way you know which parts really have been changed, avoiding unnecessary validation requests.
also, never bet on time (if i understood the settimeout stuff right). x seconds may be enough under normal circumstances, but in the worst case ...
We needed something similar for a wizard where some steps required AJAX validation. The user wouldn't be allowed to close the wizard by clicking Finish if there were any pending validations. For this we simply had a counter for pending validations, and a flag to signal if the user was wishing to close the wizard. The basic algorithm was:
If a new AJAX validation is initiated, increment the "pending" count.
When an AJAX validation returns, decrement the "pending" count.
If, upon decrementing, the pending count reaches zero, check the "finish" flag; if it is set, finish the wizard.
When the user clicks Finish, check the "pending" count; if it's zero, finish the wizard; it it's non-zero, set the "finish" flag.
This way, synchronization can be handled with just two variables ("pending", "finish").
I strongly advise against using multiple flags for each different AJAX operation; a state machine usually gets out of hand when states are tracked with multiple state variables. Try to avoid it unless it's absolutely necessary.
I also don't suggest using setTimeout to arbitrarily wait until desired conditions are met. With the counter approach above, your code will act on changing conditions, as soon as they change.
Related
I have a vanilla js setup that relies on functions being stored in a config object. The stored functions are then assigned to anchor element .onclick events (used to allow clicking headers in a table, but that's not related to the question). Those functions usually fire an alert, show a confirm dialogue, or prompt the user for a string. i.e:
const cfg = {
f1: (hdr) => { alert (`You clicked on ${hdr}`); }
}
anchorElement.onclick = cfg.f1.bind(null, anchorElement.textContent);
Everything works, but whenever one of these functions waits for a user's response (even just clicking OK on an alert), the browser (Chrome in this case) is firing the '[Violation] 'click' handler took ms' message in the console.
I have searched for this issue and tried the various workarounds for similar issues (involving promises, async, and timeouts), but the message persists as seen here:
Fiddle
I have been through all of the 'similar questions' that pop up as I type this. I get that this is not a code-breaking issue and that the most common suggestion is to just ignore it if you know it's not a long-running process that you need to tweak. But the presence of the message indicates to me that there should be a "right" way to handle a click event when you are expecting it to take an unforeseen-but-definitely-longer-than-50ms amount of time.
So, what IS the proper way to wait on a dialogue box in an onclick function?
The right way is probably to never use alerts and other process blocking methods as they are annoying for the user.
Modals are usually created inside the website
I have a web page that shows remote asset data (for example weather station data) and that does background XMLHttpRequest()'s every 5 seconds to our server and reloads the page if new data from the remote asset has been received. This has all been working fine for years.
The page also has numerous links and submit buttons that can be used to go to other pages or submit commands to the server (which then sends a command to the asset). Issue I'm having is that some of the commands the server then executes involve calls to 3rd party web services, some of which can occasionally take up to 30 seconds to return or time out. But in the meantime if new data came in from the asset the background JS function reloads the page, thereby interrupting and cancelling the new http request that the user initiated.
I could probably work around this by adding onclick or onsubmit tags to every link and submit button to call a function to disable the timer, but as there can be dozens of links on the page I am hoping there might be a simpler, more elegant way where one simple function can tell me if the user clicked on something and thereby initiated a new active http session.
I enable my script by doing a setTimeout('myCheckServerFunction("'+url+'")',5000); from the html head. If the server then tells it there is new data it does a setTimeout(function(){location.reload();},5000);
So I'd like to disable the JS timer and prevent any reload if the user has clicked any link or button and thus if a new http session is active. Does there exist a function like this? eg. something like "window.isNewHttpRequestActive()" ? Or maybe there's a way I can check if the window.location changed? (not sure if that would get updated before the new http request is complete.)
Otherwise I could maybe attach a addEventListener() to every link and submit button on the page but I'm a PHP dev not JS so if anyone could recommend the best way to parse the DOM and attach a listener to every link and submit button that would work too.
I did try looking for events that "bubble" up to higher layers eg. the body element and that will catch link clicks but also catches any click even just a click on any blank area, So not sure how well that would work as I'd still need to filter that event to determine if it actually came from a link or button. Thank you.
Listening to all click events on body isn't necessarily a bad idea.
EDIT: As gre_gor pointed out in comment, it might be. The perceived target of the click is not always the link or button if other elements are inside of them.
So my original method, which was using event.target.tagName is to be avoided.
The following code would add an event listener for click on every a element of the document, and let you cancel the timer if it is set :
for (let element of document.getElementsByTagName("a") {
element.addEventListener("click", (event) => {
if (relocationTimeout !== undefined) {
clearTimeout(relocationTimeout);
relocationTimeout = undefined;
}
});
}
Up to you to adapt the selector in the loop to fit your needs.
Of course don't forget to store the timeout reference in a variable when you set it :
let relocationTimeout = setTimeout(function(){location.reload();},5000)
Given that JavaScript is running synchronously (no setTimeout) to add or remove HTML elements, are HTML changes supposed to be shown to the user before it's completely finished?
More importantly, if the JavaScript is changing elements that can be included in form submission (e.g. adding multiple textarea elements), can user click submit button while it's in progress and submit an incomplete request? (Some textarea elements are included in request and some textarea elements are not.)
Something like this:
<form action="" method="post"><input type="submit" value="Submit"></form>
And when a user submit it, some function happens to be in progress.
function happens_to_be_running_and_user_clicks_submit(){
the_form_element.insertBefore(document.createElement('textarea'),the_form_element.firstChild).value='first textarea value';
the_form_element.insertBefore(document.createElement('textarea'),the_form_element.firstChild).value='second textarea value';
}
(It seems there are a lot of discussion about reflow hurting performance. But I'm still not sure if there exists any requirement or maybe it just depends on the browser. Especially about submitting form, regardless of whether it has anything to do with reflow or rendering.)
I think your confusion can be explained by how you've named your example function happens_to_be_running_and_user_clicks_submit. I presume you expect that it is possible for a function to be running when a user clicks a button.
This is not possible. At least not without using web workers. And even then, web workers cannot alter the DOM so by the time the web worker sends a message back to the main thread to alter the DOM it becomes impossible again.
Javascript is single threaded. Therefore only one thing can happen at a time. When a user does anything (move a window around, click a button, move the mouse..) the OS will send that event to the application's event queue. Javascript (or rather, the browser) reads this queue to process events. But since it's single threaded it only processes events when it is not busy.
So. That leaves us with ONLY two possibilities.
Your function somehow executes immediately before the user clicks the button. Then everything the function does will be rendered and active when the user clicks the button.
Your function somehow executes immediately after the user clicks the button. Then nothing the function does will be active when the user clicks the button.
There is no 3'rd state. It's either all or nothing. At least not without something asynchronous like setTimeout or ajax.
I have an Angular form with dynamical many subforms. The first form won't get resetted after submit and the subforms will be resetted on every submit.
We can use this whole form more than once, if the user doesn't reload the page.
So after validation and submit, I don't reset the first form, but let the user click it through again and he can add some additional subforms (if nothing changes).
The validation only appears if the user clicks on submit, so the scope variable subFormSubmitted gets true and the required error is still true. e.g.
subForm.salutation.$error.required && subFormSubmitted
On first pageload - everything works fine. When I try to submit the subform, without entering something, the required validation gets shown.
The problem is, after he submitted the form the first time, and he doesn't change anything in the first form and he gets to the dynamical forms the second time, and just click on submit, without entering something, the model doesn't get updated and no validation is shown, although the scope variables has the right value.
The variables
subForm.salutation.$error.required && subFormSubmitted
evaluates to true when I check it in the webdeveloper. However, when I focus an input and type something in, the required validation immediately appears on the other inputs. Also, when I change something in the first form - and then enter the subforms, the validation shows correctly when I press submit.
So I thought, that could be a problem with applying the scope.
What I did after some try and errors, I got a solution that works:
I added
if (!$scope.$$phase) {
$scope.$digest();
}
to the scope function that gets called when I press submit.
This works fine, but after some research, I found out that this is an anti pattern:
Why is using if(!$scope.$$phase) $scope.$apply() an anti-pattern?
So, can someone help me and tell me what's the "right" way to solve this problem?
Basically I controll the visibility of the forms with ng-show=firstFormSubmitted.
$scope.addSubForm = function() {
$scope.firstFormSubmitted = true;
I hope you could understand my problem, sorry for the bad english
$scope.$apply and $scope.$digest are meant be executed in callbacks that run asynchronously (after the initial digest cycle): AJAX requests, setTimeout/setInterval/setImmediate, non-$q promises, DOM event listeners, etc.
It is Angular's job as a framework to do it automatically in all asynchronous built-ins ($http, $timeout, directives), and it handles the job. Well mannered third-party extensions do the same thing.
The reason why !$scope.$$phase && $scope.$digest() can be considered an antipattern is that in well-shaped app the developer always knows if the code is performed on digest or not, thus this check is redundant. And if it's not, this is the indicator of some flaws in application design.
I've found variants of this problem on Stack Overflow but nothing my matches my specific circumstance. Hopefully someone has some insight.
Right now I'm working on a web application where there is a button (technically an anchor tag) that spawns a list of items when pressed. The issue is, if the user presses this button rapidly twice in a row, the list will be spawned twice-- the button is meant to clear the list before spawning it to prevent duplication, but something about the way the scripts interact is causing this bug. The button spawns the list by making an ajax call to a server.
Now, I've tried fixing this bug by flipping a boolean value to 1 when the button is pressed, and making the button do nothing until it is 0 again. This seems not to work regardless of where in the code I set the value to 0 again: I've tried putting it at the end of the ajaxGet function, as well as after page load, but neither solution works.
Ideally, I would like a way for the button to become enabled as soon as the page is completely finished loading and rendering. Ultimately, what's needed is a way of preventing the user from pressing the button twice in a row. I've considered using a timer for this, but I'd prefer not to have to resort to that.
Any ideas? Let me know if you would like code snippets.
===========================================
EDIT: Thanks everyone for your answers! I used a variant of Fibrewire's answer to solve the problem, and it works great. At the beginning of the method that the button calls, I put the following code:
if (actionsDisabled == 1) {
return;
}//if
else {
actionsDisabled = 1;
setTimeout("actionsDisabled=0;", 1000);
}//else
Where actionsDisabled is a global boolean. It might not be as airtight as it could be (in particular, you'd hit a problem if the list took more than a second to load), but it's elegant and functional, and has the added bonus of reducing server requests (if traffic ever became a problem, you could restrict calls to once every 5 or 10 seconds or whatever). Thanks again!
you can disable the button after the first click
Disabling the button after once click
and if you need the user to be able to click the button again in the future you can use the setTimeout() method to re enable it after a brief pause
http://www.w3schools.com/jsref/met_win_settimeout.asp