Delay a javascript function until a css animation is complete - javascript

I am trying to make a modal object library that will create and open a div, iframe, img, similar to colorbox. I am doing this in pure javascript, so please do not recommend jQuery.
The problem is, when a user creates a new modal using var myModal = new modal(options, width, height), I want it to check if a modal already exists, close it, wait for the close animation, then continue to create the new modal. I can already do everything, but I'm having an issue waiting to create the new modal until the old one is gone. I am aware of webkitTransisionEnd and firing custom events, but that is not the issue. I need the actual code to wait until the old modal is finished closing until it continues on to finish the rest of the function and still return the correct object to the user. Here are some of the things I've tried:
Creating a transisionEnd listener waiting for the animation to end then creating the new modal. (this worked but considering it then becomes a nested function, it's hard to return the correct object).
Using a try, catch block. (this didn't work for my purposes)
Using a countless number of variations of the same thing where I use recursive functions
If anyone has ideas, please feel free to post them. I have tried a lot of things, but apparently not the one thing that I need to. Thanks.
EDIT:
I was able to figure it out. All I had to do was attach a transitionEnd listener to the modal that is already open, then create an additional function outside of the class that would then recall the modal with the same constructor. The code looks a bit like this:
function create(options, width, height) {
return new modal(options, width, height);
}
function modal(options, width, height) {
if (modal != null) {
modal.close();
modal.addEventListener('webkitTransitionEnd', function() {
create(options,width,height);
});
}
return;
}

var animationDuration = 1000;
setTimeout(function(){
// Animation done!
}, animationDuration);

You can't cause code to wait (e.g. pause execution of the current thread of execution) until some future event occurs. Javascript simply does not support that or work that way. It does not have a way to block the current thread of execution other than a couple modal functions like alert().
What you can do is use callbacks to notify some calling code of a future event. But, the calling code will register its callback and be returned to immediately and continue executing so the calling code has to be written to handle the callback implementation.
If you're trying to do all the work inside your library, then it should not be that tough. When the caller creates a new modal, you just have to check for a pre-existing modal dialog. If one is not up you proceed as normal. If one is up, then you register a callback notification with the previous one, store the contents of the constructor, but don't actually create the new modal dialog. Then, when your callback gets called to indicate the previous modal dialog has completed, you finish putting up the new modal.
If these modal dialogs are all of your own creation, then you need to implement completion notification on them so that when they are closed, they can notify any listeners that they're done now. If they use an animation to close and you want to wait for the close notification until the animation is complete, then you can implement that also. If you're using CSS3 animations, then as you appear to already know, you can use the transtionEnd event to know when an animation is done or if you know the timing of the animation and you don't need to be ms precise, you can also just use a setTimeout() to know approx when the animation is complete.

Related

How do I wait for my scroll function to finish before starting the next function?

I have a chrome extension that scrolls to the bottom of a users page in order to load all their items (the site uses lazy load)
function makeProgressCountingAllItems() {
// start off by counting all the items you see on the page
allItems = document.getElementsByClassName('items');
// scroll the last item currently loaded
allItems[allItems.length - 1].scrollIntoView();
document.dispatchEvent(new MouseEvent('scroll'));
}
I then want to get all the items on the page (including the newly loaded ones) and put them in an array
function selectItems() {
return document.getElementsByClassName('items')
}
But if I call both of these functions like this
makeProgressCountingAllItems();
selectItems();
The problem is my selectItems function runs before the makeProgressCountingAllItems function gets a chance to scroll to the bottom of the page.
How do I run the second function after the first function is complete?
I think that your Question's title it's not appropiate for the feature you're asking, because ideally you want to listen for new items to be added you don't need to worry about waiting the scroll function to finish.
For listening changes in the DOM you can use a MutationObserver.
See this question as reference.
If you want to wait until the scroll function finishes it's not possible with the current API.
At this moment there's an open request to include a callback when scrollIntoView animation ends, you can see the thread of this request here and a workaround for your question here.
Adding a setTimeout is the best option you have, here's an example:
makeProgressCountingAllItems();
setTimeOut(()=> { selectItems(); },500);

How can I reuse a cloned element?

I have a Bootstrap modal on my page. Basically, what happens is the user picks some options on the page, clicks a go button and that modal pops up and gets populated with the live output of the job they started.
After the job runs, I'd like for the user to be able to close the modal, choose more options, and run the job again. The problem is, I can't seem to get rid of the output from the previous job.
I tried this answer, which was to clone the div and use replaceWith() to restore the content to it's original state. This works for the first two times (job runs once, then when you start another the modal is back to it's original state), but for any time after that, the modal pops up with the content of the previous run until it's text gets overridden.
I have this at the beginning, to capture the contents before anything is done:
$(document).ready(function() {
modalHold = $("#postModal").clone();
});
And, this runs when the modal closes:
$('#postModal').on('hidden.bs.modal', function (){
$("#postModal").replaceWith(modalHold.clone());
})
I would've expected the replaceWith(modalHold.clone()) to replace it with a new clone of the original element, however it seems that I'm still modifying the original. Any help would be appreciated, or if there's a better way of doing this I'd be glad to hear it.
Bootstrap does some javascript magic with the Modal, so I guess you can't just clone whole the Modal's HTML. As a workaround you may try to play with class="modal-body" node only, clone and replace it.
But the truth is on another way. You need to implement a function which would reset your inputs and call it each time the Modal is being hidden.
var modalDefault = {
input1: '',
input2: 'Hello!'
};
var resetModal = function() {
$('#modalInput1').val(modalDefault.input1);
$('#modalInput2').val(modalDefault.input2);
}
// ...
$('#postModal').on('hidden.bs.modal', resetModal);
Not sure why I didn't think of this to begin with, but dhilt's answer helped point me in the right direction. The idea of creating defaults and just switching back to those could be helpful in some cases, but I had some content (including job info and a loading bar) inside the modal that I'd really like to be displayed each time a job starts, until it is done and the output can be displayed.
Instead of doing any fancy cloning, I placed that content into a div and just grabbed its innerHTML:
$(document).ready(function() {
modalHold = $('#jobOutputHolder').html();
});
When the .load () runs, it will update #jobOutputHolder with the output of the job. Then, on hide of the modal:
$('#postModal').on('hidden.bs.modal', function (){
$('#jobOutputHolder').html(modalHold);
})
With this method, I can run a job, see the loading screen, see the job output, close the modal, and repeat as many times as I need without ever seeing the output of previous jobs.

JS - Pause custom alert()

I've edited the default alert() function like this:
window.alert = function(str){
//custom alert
}
Essentially, the new function will show an HTML <div> modal.
Backstory:
I want to answer to this question because I am having a problem. The alert is hidden and will show when the custom alert function is called. So the custom alert is basically showing the element and changing it's text. Therefore, when I have multiple alert() calls, only the last message is displayed! :(
But, unlike the default alert box. It of course won't pause the webpage until the alert goes away like the default alert() function.
Is it possible to imitate this "pause the webpage" behavior?
Also, is there another way other then using setTimeout() to check if isCustomAlertOpen == true to delay another custom alert from triggering until the current one is dismissed?
Edit:
Is there a way to queue up the custom alerts?
I don't mind jQuery, but I thought it might be overpowered here.
My question title is bad. Can someone think of a better title for me please?
There is no way to block the execution because JavaScript is asynchronous (except obviously 3 modal functions and XmlHttpRequest).
For the same reason, you can't wait for a previous alert to be closed, but you can use events to create a stack of alerts using this pattern:
window.alert = (function() {
var stack = [];
var showNextAlert = function() {
var div = document.createElement("div");
/* Here, configure the div, show the string from stack[0] and add it to the document ... */
var okButton = /* ... */;
okButton.addEventListener("click", function() {
div.parentNode.removeChild(div);
stack = stack.slice(1);
if(stack.length > 0) {
showNextAlert();
}
});
}
return function(msg) {
stack.push(msg);
if(stack.length == 1) {
// Show it immediately if the stack is empty
showNextAlert();
}
};
})();
You should also use another name for this function instead of changing native properties (here window.alert).
Is it possible to imitate this "pause the webpage" behavior?
The answer to this is no. There are ways to block the ui (sync ajax call, long loop etc). But you wouldn't be able to stop these when the user has click the ok button.
A better approach would be to restructure the code so that it didn't run synchronously. Ie you wouldn't need to block the ui while waiting for the user.
Also, is there another way other then using setTimeout() to check if isCustomAlertOpen == true to delay another custom alert from triggering until the current one is dismissed?
One way to do this is: instead of setting a flag in one place and checking it repeatedly in another. You can use the concept of events. One part of code is waiting for an event to be triggered and another part triggers it. There is a library in jQuery which can do this for you or you could read up on it and write your own.

JavaScript setTimeout will not die.

I made a photogallery and is AJAX powered. Below are the relevant pieces of code.
if (isset($_POST['stopss'])){
echo'<script type="text/javascript">
clearTimeout(setss);
</script>';
}
if (isset($_POST['startss'])){
?>
<script type="text/javascript">
var setss = window.setTimeout('delayer(\''+src+'\',\''+dir+'\',\''+title+'\',\''+width+'\',\''+height+'\',\''+img+'\')',5000);
</script>
<?
}
The delayer function passes params for a new modal box window (new AJAX request).
The problem as I see it. The timeout gets started when a user clicks the Slideshow button.
Even though they press the Stop Slideshow button (this reloads the AJAX page), the timer is still running (in the background) and in clearTimeout(setss), setss is no longer available (because of the new AJAX page request) so the clearTimeout fails to clear.
Is there any way to kill the timeout running in the background? Even if the modalbox window is closed, 5 seconds later it opens and happily keeps playing the slide show.
btw, this is the modalbox call.
Modalbox.show('../b-gal-scripts/ajaxgal.php', {title: title, overlayOpacity: .1, width: width, height: height, method: 'post', params: {src: next, dir: dir, img: img, ssimg: next} }); return false;
The problem
When you reload the page, the old JavaScript code (for the previous request) stops running and the new one is executed.
The problem is that you first call clearTimeout(setss) and later define what setss really is.
A couple additional problems
A few tips:
Do not mix quoting styles, you are creating a mess this way.
If you want quotes within quotes, just use the fact there are different quotes like that:
var setss = window.setTimeout("delayer('"+src+'","'+dir+'","'+title+'","'+width+'","'+height+'","'+img+'")',5000);
...and preferably get used to event-based programming and closures when coding JavaScript, like that:
var sets = window.setTimeout(function(){
delayer(src, dir, title, width, height, img);
}, 5000);
Final version of your code
Your code should look like the following:
if (isset($_POST['startss']) && !isset($_POST['stopss'])){
?>
<script type="text/javascript">
var setss = window.setTimeout(function(){
delayer(src, dir, title, width, height, img);
}, 5000);
</script>
<?
}
It makes use of the fact, that you do not need to first output a script that is executed only to immediately stop it if not needed. Just make PHP condition to check if you want it to be executed, the output it.
Second, the JS has been changed from using strings (which is totally messy) to using anonymous functions. Take a closer look into that.
Did it help?
I'm having a little difficulty understanding your problem, but I hope this helps.
When a page is reloaded, the previous instance of the page, and any state associated with it ceases to exist. As a result, the timeout has been stopped. Essentially, if the global setSS doesn't exist, then neither does the timeout to which it refers, therefore, that timeout cannot be responsible for starting the slideshow.
When an active page is served from something like PHP, the work of the server (WRT the creation of any content of the page) is done. No more content can subsequently be written to the page. Your clearTimeout is essentially useless, as it could only clear a timeout that is set during the load of the page. It won't write a new script element into the existing page.
Variables are not shared across pages, so if you are setting the timeout in one page, and clearing it in another, that will not work.
So, in order to clear the timeout, you need to call clearTimeout in the same page (and instance of that page) as the setTimeout, and that call must be executed after the setTimeout.
If none of the above apply to your situation, then is it possible that $_POST['startss'] and $_POST['stopss'] are set in the same request? If so, the new page will create a new timeout after the attempt to clear it, and will therefore display the slideshow.
(as an aside, using a closure to create a function to pass to setTimeout will be more readable than compiling a string that calls a function).

Run Code After Function is Finished OR DIV Appears

I have written a Userscript for Facebook and it groups similar notifications together. Now, using just a static HTML page with old notifications I had, the script works 100%.
Here's the problem: the DIV that holds notifications by default, has no notifications in it until the user clicks on the Notification button. Facebook has an inline onclick event that displays the DIV (Notifications menu) and uses AJAX to grab the notifications. Since my userscript only runs on startup, it finds nothing there - it has no effect on the page. I tried using a .click() event but right when you click the Notifications button it runs my code. The thing is, Facebook is still running it's AJAX request for the notifications, meaning that there are still no notifications for my script to work with, making it have no effect.
I don't want to use a generic setTimeout because I don't want the user to have to see the notifications before and then suddenly see them after. Is there a way to monitor the DIV so once the notifications are added, it runs? Or that once Facebook finishes it's AJAX request, it will run my code?
You can setInterval() to monitor the div's innerHTML, once populated you can run.
Here's the problem: the DIV that holds notifications by default, has no notifications in it until the user clicks on the Notification button. Facebook has an inline onclick event that displays the DIV (Notifications menu) and uses AJAX to grab the notifications.
...
I don't want to use a generic setTimeout because I don't want the user to have to see the notifications before and then suddenly see them after. Is there a way to monitor the DIV so once the notifications are added, it runs?
Yes there is:
var notificationDiv = document.getElementById(...);
var lastTimeoutID = null;
var main = function(evt){
// evt.target is the inserted element
// if you expect more elements, then do the following:
lastTimeoutID = setTimeout(function(){
lastTimeoutID = null;
// uncomment below block if you only need to do the work once.
/*
notificationDiv.removeEventListener("DOMNodeInserted", main, false);
*/
// do your work here.
}, 500); // waiting 500ms for next element to be added, otherwise doing work.
if (lastTimeoutID) {
clearTimeout(lastTimeoutID);
lastTimeoutID = null;
}
};
notificationDiv.addEventListener("DOMNodeInserted", main, false);
All of the timeout code above might be removable depending on what it is you're userscript is doing.
Maybe you can add a setTimeout loop. Something like
setTimeout(function(){
/* check for divs */
if(/* divs are found */){
//apply code
}
else {
setTimeout(/* loop again */);
}
},1000);

Categories

Resources