Reputation: 124
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?
Upvotes: 1
Views: 1046
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:
!$target.closest('#sSearch').length
will evaluate true
;$('#sSearch').not('.collapsed')
will evaluate true
Now say you want to show the search input again, so you click the button:
!$target.closest('#sSearch').length
will evaluate false
;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
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