I would move all of the functionality into a single document ready handler. I will use the short-hand notation.
$(function () {
/* All code goes in here. */
});
I will use less verbose selectors for creating my jQuery objects and I will cache them by assigning them to variables.
var $div = $('#TheDiv');
var $form = $('#TheForm');
var $window = $(window);
I think that hiding the form on the textarea's focusout event is a bad idea because this event could be triggered by the user tabbing out of the textarea. Hiding the form at this point would prevent the user from submitting the form.
I think a better approach is to take our direction directly from your requirements and hide the form when the user clicks outside of it. We can do this by attaching a click handler to the $window. In this handler we will call hideForm only if the clicked target was neither the form element nor an element within the form.
$window.on('click.TheForm', function (e) {
if ($(e.target).is($form)) { return; }
if ($.contains($form.get(0), e.target)) { return; }
hideForm();
});
Notice above that the click event has been namespaced with '.TheForm'. Namespacing will allow us to easily remove the event handler without removing any other click events that may have been attached to the window. I will wrap the $window click handler code from above into a function so that we can re-attach this handler as we please. I will also create a function for removing the handler.
var attachWindowClick = function () {
$window.on('click.TheForm', function (e) {
if ($(e.target).is($form)) { return; }
if ($.contains($form.get(0), e.target)) { return; }
hideForm();
});
};
var removeWindowClick = function () {
$window.off('click.TheForm');
};
If we want to hide the form when it is submitted, it will be better to listen for the submit event on the form rather than the mousedown event on the submit button, because the user can submit the form without the mouse, for example, by pressing Enter.
$form.on('submit', function (e) {
/* Do AJAX stuff here. */
hideForm();
e.preventDefault();
});
In the showForm function, we will attach our window click listener so that we can capture clicks while the form is displayed. Note that we do not want our click of the #TheDiv to bubble-up and trigger our handler, so we will ensure our call to attachWindowClick is outside of the current call stack by using setTimeout with a delay of 0 milliseconds.
var showForm = function () {
$form.show();
$div.hide();
$form.find('textarea').focus();
setTimeout(attachWindowClick, 0);
};
The final code looks as follows:
$(function () {
var $div = $('#TheDiv');
var $form = $('#TheForm');
var $window = $(window);
var attachWindowClick = function () {
$window.on('click.TheForm', function (e) {
if ($(e.target).is($form)) { return; }
if ($.contains($form.get(0), e.target)) { return; }
hideForm();
});
};
var removeWindowClick = function () {
$window.off('click.TheForm');
};
var hideForm = function () {
$form.hide();
$div.show();
removeWindowClick();
};
var showForm = function () {
$form.show();
$div.hide();
$form.find('textarea').focus();
setTimeout(attachWindowClick, 0);
};
$form.on('submit', function (e) {
console.log('do ajax');
hideForm();
e.preventDefault();
});
$div.on('click', showForm);
});
hideFormtakes an ID as argument, butshowFormdoes not.This says to me thatshowFormhas the specific form selector hard-coded into it, whereashideFormdoes not. I think these functions should be made consistent and either both should accept the selector argument or both should not. \$\endgroup\$