wheel58m
wheel58m

Reputation: 124

Is there a way to toggle the behavior of a submit button with jQuery? (Ex. Show/Hide/Submit)

I'm trying to toggle a button to either submit on click or reveal an input field depending on if the user clicks the document to hide/collapse the input field. The issue is that after the user dismisses the input field the submit button doesn't return to a toggle behavior; clicking the button to again open the input field results in form submission.

I need the button, when clicked, to trigger a search field to slide in. Once the input field is visible, the button will submit instead of toggle, unless, the user clicks on the document to hide the field. Currently, the initial click will open the input field, clicking on the document will collapse the field. The second click after either previous action will result in 'submit'.

I believe, based on my little understanding, that the preventDefault() function isn't occurring on the second click or, .off('click') needs to be toggled somehow?

HTML:

  <form action="" id="sSearch" class="collapsed">
    <input type="search" id="sSearch-Field" placeholder="Search">
    <button type="submit">
      Open
    </button>
   </form>

JS:

$('#sSearch button').click(function (event) {
  event.preventDefault();
  $('#sSearch').removeClass('collapsed');
  $('#sSearch button').off('click');
});

$(document).click(function (event) {
  $target = $(event.target);
  if(!$target.closest('#sSearch').length && $('#sSearch').not('.collapsed')) {
    $('#sSearch').addClass('collapsed');
  }
});

I don't understand why the preventDefault() function doesn't work on the second click? Based on what I've seen in similar questions, the off() and unbind() functions should reset the click and default behavior. Is this true?

CodePen Example

Upvotes: 1

Views: 1046

Answers (2)

Don&#39;t Panic
Don&#39;t Panic

Reputation: 14520

There's already an answer here, but it doesn't explain why your code isn't working. From your question it is clear you are specifically trying to understand the problem, so let's solve it! :-)

You asked:

... the off() and unbind() functions should reset the click and default behavior. Is this true?

In this case, yes, that is pretty much what happens. More generally, off() will remove an event handler. In this case it removes the click handler that was added to the button.

So where's the problem? The first time that button handler runs, it removes itself from handling any future clicks. That means you'll never be able to display the search input again (which is what the handler normally does). If you click on the document to hide the search input, and then need to show it again, you can't. The handler which does that is no longer listening for clicks on your button!

Let's trace how that happens:

  • If the second click is on the document:

    • first handler is not attached to any events, will not fire;
    • second handler does fire;
    • !$target.closest('#sSearch').length will evaluate true;
    • $('#sSearch').not('.collapsed') will evaluate true
    • test passes, search field is hidden;
    • GOOD! This is what you want;
  • Now say you want to show the search input again, so you click the button:

    • first handler is not attached to any events, will not fire;
    • second handler does fire;
    • !$target.closest('#sSearch').length will evaluate false;
    • test fails, second handler does nothing;
    • neither handler did anything, default action was not prevented - the form will submit, just like a normal HTML form without any Javascript at all;
    • BAD! You this is not what you wanted.

Side note: $('#sSearch').not('.collapsed') works, but for readability and consistency it should probably be $('#sSearch').not('.collapsed').length, just like the first condition.

So, how to get the behaviour you want?

To stick with the approach you are using, you would need to re-attach the first event handler when the search field is hidden, so that the page goes back to the initial state, just like it is at page load.

To do that, I would extract the code into a function so that you can call it from 2 places, to avoid having to duplicate it:

// New function we can call whenever we need it
function showSearch (event) {
    event.preventDefault();
    $('#sSearch').removeClass('collapsed');
    $('#sSearch button').off('click');
}

// Call our function when button clicked
$('#sSearch button').click(showSearch);

$(document).click(function (event) {
    $target = $(event.target);
    if(!$target.closest('#sSearch').length && $('#sSearch').not('.collapsed')) {
        $('#sSearch').addClass('collapsed');

        // Re-attach the function to button clicks, so page goes back to
        // the initial state, before any button clicks
        $('#sSearch button').click(showSearch);
    }
});

Here's a working JSFiddle (sorry I switched from CodePen, I'm more used to it).

Alternatively, while the approach you've taken works fine, I'd maybe opt for something a little simpler. Rather than adding or removing event handlers based on user activity, I'd instead track the current state of the form and use that to work out what to do when a click happens.

We need some kind of variable to track the state ... but if you take a step back, you already have such a variable! The collapsed class tells us the current state of the page. You can use jQuery's .hasClass() method to check if the class is present or not, and that will tell us the state the page is currently in:

  • For clicks on the button, if the input currently has the collapsed class, you want to display it, so you prevent the submission and remove the class. If the input does not have that class, you don't do anything, and allow the submission to happen.

    $('#sSearch button').on('click', function(event) {
        if ($('#sSearch').hasClass('collapsed')) {
            event.preventDefault();
            $('#sSearch').removeClass('collapsed');
        }
    });
    
  • For clicks not on the button, when the input is shown, you want to hide it; when the input is hidden you do nothing.

    $(document).on('click', function (event) {
        $target = $(event.target);
        if(!$target.closest('#sSearch').length && !$('#sSearch').hasClass('collapsed')) {
            $('#sSearch').addClass('collapsed');
        }
    });
    

Here's another JSFiddle, using this different approach.

Upvotes: 2

Kalimah
Kalimah

Reputation: 11437

You can add a flag variable to determine if the input is shown or not. Also, you need to use .on("click") instead of .click() to handle live changes.

Here is the code:

let readySubmit = false;

$('#sSearch').on("click", "button", function(event) {
  if (readySubmit == false) {
    event.preventDefault();
    $('#sSearch').removeClass('collapsed');
    $('#sSearch button').off('click');
    $('#sSearch button').html("Submit");
		
		readySubmit = true;
  }
});

$(document).click(function(event) {
  $target = $(event.target);
  if (!$target.closest('#sSearch').length && $('#sSearch').not('.collapsed')) {
    $('#sSearch').addClass('collapsed');
    $('#sSearch button').html("Open");
		
		readySubmit = false;
  }
});
#sSearch input {
  position: absolute;
  right: 40px;
  width: 167px;
  height: 38px;
  padding-left: 0.75rem;
  padding-right: 0.75rem;
  border: 1px solid #ced4da;
  transition-property: width, height, padding-left, padding-right;
  transition-duration: 0.25s;
  transition-timing-function: linear;
}

button {
  transition: all 0.25s linear;
}

#sSearch.collapsed input {
  width: 0;
  padding-left: 0;
  padding-right: 0;
  border: none;
  transition: all 0.25s linear;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form action="" id="sSearch" class="collapsed">
  <input type="search" id="sSearch-Field" placeholder="Search">
  <button type="submit">
    Open
  </button>
</form>

Upvotes: 0

Related Questions