2

I'm using the WPForms plugin on my WordPress site. One of my forms has a hidden field which appears when you click a radio button elsewhere on the form, and disappears again when you click another radio button.

The hidden field has a class of .wpforms-conditional-hide when it's hidden, which is set to display:none. And the class magically changes to .wpforms-conditional-show, which is set to display:block, when the field is revealed.

I would like to animate the hiding and showing of this field, for example by having it slide in and out of view. I can't use CSS, because CSS isn't able to do animations/transitions with the "display" attribute, and the hidden field doesn't have a set height. I already have jQuery on my site, so I'd like to find a way to achieve this with jQuery, eg. using something like slideToggle or similar.

The thing is, I don't want to have the animation bound to the click action on the radio button. Particularly because I'd like this to be able to work on any hidden field on any form. So, if it's possible, I'd simply like a way to use slideToggle (or something similar) whenver the hidden field's class changes from .wpforms-conditional-hide to .wpforms-conditional-show and back, or alternatively whenever its CSS attribute changes from display:none to display:block and back.

Is that possible?

[UPDATE]

For the record, here is the entire function I'm now using, which incorporates the code and suggestions that @blex has provided his answer and his comments. The function below will work "as is" with WPForms on WordPress.

function slide_hidden_wpforms_fields() {
if ( wp_script_is( 'jquery', 'done' ) ) {
?>
<script type="text/javascript">
jQuery( document ).ready(function( $ ) {
$( document ).one( "wpformsProcessConditionals", function() {
    var classname = 'wpforms-conditional-show';
    $( '.wpforms-field' ).each( function( i, el ) {
        var wasVisible = $( el ).hasClass( classname );
        var obs = new MutationObserver( function( mutations ) {
            mutations.forEach( function( mutation ) {
                if ( mutation.attributeName === 'class' ) {
                    isVisible = $( el ).hasClass( classname );
                    if ( isVisible !== wasVisible ) {
                        wasVisible = isVisible;
                        $( el )[isVisible ? 'hide' : 'show']().slideToggle( 200 );
                    }
                }
            });
        });
        obs.observe( el, {
            'attributes': true
        });
    });
});
});
</script>
<?php
}
}
add_action( 'wpforms_wp_footer_end', 'slide_hidden_wpforms_fields' );
GermanKiwi
  • 23
  • 4
  • From your description, I understand that you want to _react_ to this change of class. Is that right? In your case, it would be easier to use `$('#field').slideDown()` and `slideUp` instead of using a class (even though a class is more elegant), but that would only be possible if you have access to the code toggling the class – blex Jan 03 '20 at 22:48
  • I'm not entirely sure what "react" means in the jQuery sense, but it sounds correct - I do want to "react" (in the dictionary sense of the word) to the change of class by having the hidden field slide into view instead of instantly appear in view. Unfortunately I don't have access to the code toggling the class though - that would be something built into the WordPress plugin. I'd also prefer to base the solution on the class change, not on the ID, because I'd like it to work on all hidden fields on the page (and other pages) which use those two switching classes. – GermanKiwi Jan 03 '20 at 22:57

1 Answers1

2

Edit For the full copy and paste answer, see @GermanKiwi's

You can use a Mutation Observer to detect a change of class. Here is an example:

var classname = 'wpforms-conditional-show';
// For each input (use your own selector)
$('input').each(function(i, el) {
  // Save its state
  var wasVisible = $(el).hasClass(classname);
  // Create a mutation observer
  var obs = new MutationObserver(function(mutations) {
    // When mutations are received, for each of them
    mutations.forEach(function(mutation) {
      // If the mutation is a change of class
      if (mutation.attributeName === 'class') {
        // Check the presence of that class
        isVisible = $(el).hasClass(classname);
        // If that changed (and only for this specific class)
        if (isVisible !== wasVisible) {
          // Update the state
          wasVisible = isVisible;
          // Hide the element before sliding it down, or vice versa
          $(el)[isVisible ? 'hide' : 'show']().slideToggle(200);
        }
      }
    });
  });
  // Start the observer
  obs.observe(el, {
    'attributes': true
  });
})

// Just for the demo
var i = 0,
    inputs = $('input');
setInterval(function(){inputs.eq(i).toggleClass(classname);i=(i+1)%inputs.length;},100);
input { display: none; }
.wpforms-conditional-show { display: block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text" placeholder="Hello">
<input type="text" placeholder="World">
<input type="text" placeholder="And">
<input type="text" placeholder="Welcome">
blex
  • 24,941
  • 5
  • 39
  • 72
  • Couple of follow-up questions: (1) using your script, whenever I now reload the page, the hidden field is momentarily visible before it slides out of sight. Is there any way to prevent this? (2) Someone answered a similar question at https://stackoverflow.com/a/41555175/3019930 with a one-line jQuery snippet, which I thought would work - and it's a lot shorter! Is something like that not possible, eg. detecting a change of CSS attribute (from display:none to display:block) rather than detecting a change of class? – GermanKiwi Jan 04 '20 at 00:06
  • Another question: after examining the code of the plugin I'm using – WPForms – I can see that it's using .addClass() and .removeClass() to switch the class of the hidden field from ".wpforms-conditional-hide" to ".wpforms-conditional-show". Does that mean I could use these as a trigger for the slideToggle, and would that be a better approach? For example something like: .on( "addClass", function() { do stuff } – GermanKiwi Jan 04 '20 at 01:39
  • @GermanKiwi (1) I cannot be sure without seing the page, but maybe the plugin changes classes right after the page was loaded. Then, maybe you need to delay the above script. (2) This one-liner is close to the one I used: `$(el)[isVisible ? 'hide' : 'show']().slideToggle(200);`, however, the answer you linked to does not detect anything, it just animates. My answer could be tweaked to detect a change in `display`, though. (3) There isn't an `addClass` event fired. For that, you would need to trigger it yourself [by editing the plugin's code](https://stackoverflow.com/a/1950052/1913729) – blex Jan 04 '20 at 14:34
  • Hi @blex, sorry for the delay in replying, and thanks for your helpful tips so far! Your theory about the plugin changing the class after the page was loaded, sounds like it could be the culprit. I've just created a test form and I wonder if you could take a look and see if that's the reason for the hidden field being momentarily visible whenever the page is reloaded? And if that is the reason, how would I go about adding a delay to your wonderful script? I'll post a link to my test form in a separate comment here (so I can delete it later).... – GermanKiwi Jan 25 '20 at 16:36
  • Hi @GermanKiwi, I can see that the plugin does indeed change classes on `$(document).ready`. You could do the same, but you cannot be sure whether your code will be executed before or after the initial classes are set up... But, I also see it triggers a `wpformsProcessConditionals` event when it applies changes, which you can use! To catch the first event, you can wrap your code like this: `$(document).one("wpformsProcessConditionals", function() {/* Code goes here */});` (`.one` is important here, since you only want the code to be setup once) – blex Jan 25 '20 at 16:56
  • Also, performance-wise, I would advise you to replace `$('div').each` with `$('.wpforms-field').each`, to avoid watching the entire page – blex Jan 25 '20 at 17:01
  • Thanks @blex - that's awesome! I've incorporated the tips from your previous two comments, and it now works perfectly! The hidden field no longer flashes momentarily on page load. I also added `if ( wp_script_is( 'jquery', 'done' ) ) {` to the very beginning of my function, so that it would only load if jQuery was already loaded, and I've hooked it into the WPForms `wpforms_wp_footer_end` action which loads the script at the very end of the page. – GermanKiwi Jan 25 '20 at 23:35
  • 1
    I'm gonna paste my entire function into a second answer here, in case it helps other WPForms users to see the whole thing ready to copy-n-paste, but I'll mark yours as the accepted answer. Thanks for your help! – GermanKiwi Jan 25 '20 at 23:42
  • Hi @blex, I have a follow-up question: how can I get my jQuery script (in my question above) to work with the compatibility mode that WordPress's jQuery library uses? Normally the solution is to use a document ready function like this: `jQuery(document).ready(function($){` which allows the '$' to still be used throughout the script itself. But in my script above, if I change the first line of the script to the following, it does not work: `jQuery( document ).one( "wpformsProcessConditionals", function($) {` - is it because it doesn't include `.ready`? – GermanKiwi Jun 30 '20 at 01:23
  • Hi @GermanKiwi . `jQuery(document).one(...` will not pass a `$` parameter to your function. What you can do is `jQuery(document).ready(function( $ ) { /* Paste your code here, but never use "jQuery" again, use "$" instead */ });` For example: https://pastebin.com/embed_iframe/gAyxMXex – blex Jun 30 '20 at 18:03