0

Consider this function:

function hideElements( selector ) {
    var number = $( selector ).length;
    if ( number ) {
        $( selector ).animate( { opacity: 0 }, 1000, function() {
            *RETURN* number + " elements are now completely hidden.';
        } );
    } else return "No matching elements found";
}

var returnedMessage = hideElements( ".hideable-divs" );

The uppercase RETURN is the callback's return and so it won't work; I just put it there to convey the idea. How can I make it so the hideElement function returns something only after the animation is complete? Yes, whatever code that needs the returnedMessage variable will have to wait (synchronously(?)) until the animation is complete. Thank you!

** EDIT ** This isn't a duplicate question but rather a very specific scenario I'm dealing with:

A third-party script uses a confirm dialog to ask the user if they're sure they want to delete a div. If the user clicks OK, the element is removed instantly from the DOM.

My goal is to:

  1. Bypass the confirmation dialog so the user doesn't have to confirm.
  2. Animate the deletion so the user clearly sees it happening and, in case they clicked by accident, undo if necessary.

For this, I'm redeclaring window.confirm like in the code sample below. (To eliminate any confusion, please note that this update changes the idea behind my original question and the code block I posted above.)

var clickTarget;
$( document ).on( 'mousedown', function( event ) {
    clickTarget = event.target;
} );
window.confirm = function( message ) {
    if ( message.indexOf( 'OK to delete' ) !== -1 ) {
        var $element = $( clickTarget ).parent();
        $element.animate( { height: 0 }, 1000, 'swing', function() {} );
        return true; // the 3rd party script receives this confirmation asynchronously and removes $element before it has a chance to be animated.
    } else return confirm( message );
};

At the moment I'm achieving the effect by creating a clone and animating that instead of the actual element to be deleted. But this is rather "dirty" and I was wondering if there's a "cleaner" way to do it. Thanks!

Cœur
  • 37,241
  • 25
  • 195
  • 267
Flix
  • 345
  • 4
  • 13
  • 2
    Possible duplicate of [How to return value from an asynchronous callback function?](https://stackoverflow.com/questions/6847697/how-to-return-value-from-an-asynchronous-callback-function) – Weedoze Jan 29 '18 at 08:02
  • @Weedoze question is not duplicate; please see the more detailed update. Thanks! – Flix Jan 29 '18 at 18:34
  • Try `return false;` instead. – trincot Jan 29 '18 at 18:44

1 Answers1

1

As suggested in the link provided by Weedoze, normally you don't want to block a script on asynchronous code, but rather provide a callback.

The main difference with said link is that you want to wait till all animations are completed, not call it on each element's animation finish. In JQuery you can do that with .promise().done():

function hideElements( selector, callBack ) {
    var sel = $( selector );
    if ( sel.length) {
        sel.animate( { opacity: 0 }, 1000).promise().done(function(){
           callBack(sel.length + " elements are now completely hidden.");
        });
    } else 
     callBack("No matching elements found");
}

hideElements('div', console.log);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>a</div>
<div>b</div>
<div>c</div>

An alternative would be to make your hideElements function itself return a Promise (and return a resolved promise if no elements are found):

function hideElements( selector ) {
    var sel = $( selector );
    if ( sel.length) {
     return sel.fadeOut(1000).promise().then(function(){
         return sel.length + " elements are now completely hidden.";
        });
    } else 
      return Promise.resolve(("No matching elements found"));
}

hideElements('div').then(console.log);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>a</div>
<div>b</div>
<div>c</div>

edit

Based on the added info: letting the 3rd party code wait is a bit tricky. You can make your own code wait (e.g. with async await), but by definition the calling code should continue (not blocking the ui). What you can do is cancel the delete on the first click (return false), but remember which element the code was called for. Then in the callback, invoke the click on the same element to restart the 3rd party code and this time return true to let the delete proceed:

let clickTarget, curDelete;
$( document ).on( 'mousedown', function( event ) {
    clickTarget = event.target;
} );

let defConfirm = window.confirm; //needed to store the default functionality to avoid recursion in the call below
window.confirm = function(message) { 
    if ( message.indexOf( 'OK to delete' ) !== -1 ) {  
  if(clickTarget === curDelete)return true; //the confirm is called from the current delete, invoked by the click from the callback, return true  
        var $element = $( clickTarget ).parent(); //layout based on your example
        $element.slideUp(2000, function(){ //used slide up, but the callback can be used for any animation
   curDelete = clickTarget; //NB: perhaps an extra check is needed to see if another delete isn't started?
   $(curDelete).click(); //invoke 3rd party script by emulating click (preferably, if a method is known it is called directly)
  });  
        return false; //this result is returned before the animation finishes to stop the direct deletion
    } else return defConfirm( message ); //show default confirm window
};


//3rd party spoof
$('div > div').click(function(){
 if(confirm('dsaf asfda OK to delete aadf?'))
  console.log('3rd Party delete executing');  
});
div > div{
  width:100px;
  border: 1px solid gray;
  margin: 2px;
  cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<div id=a>click me</div>
<div id=a>or me</div>
<div id=a>or me</div>
</div>
Me.Name
  • 12,259
  • 3
  • 31
  • 48
  • Thanks, @Me.Name; I definitely learned something from this! Though your code makes what I initially described happen, I also updated the question with more specifics on the _actual_ goals I was trying to achieve. If you could take a look again I'd appreciate it very much! – Flix Jan 29 '18 at 18:33
  • 1
    Ah, that's a very specific situation. Added an alternative to spoof that functionality without actually blocking the calling code. (By first cancelling the delete and then reinvoking it after the animation finishes) – Me.Name Jan 31 '18 at 10:08
  • _"[...] cancel the delete on the first click (return false) [...] then in the callback, invoke the click [...] and this time return true"_ Genius! Works great! As for "`curDelete = clickTarget;` _[...] extra check to see if another delete isn't started?"_ - that's something only a good programmer would do. I just reduced the animation time to 500ms--ain't nobody got time to click to so fast! Thanks again for the solution. – Flix Feb 03 '18 at 15:30