Antonycx Antonycx - 2 months ago 5
Javascript Question

Detect if a form has unsaved changes before letting user focus to a different form

Is there any way to detect if user has changed from the content of one section to another in the same page?

I have a page with multiple buttons (1 form/section is opened once you click on each button), when they all are closed i can detect user is changing to another form using a click event on the button element.

My sections in this case are forms loaded when you click on every button in the page. pure jquery to load the content via ajax inside them.

The problem is when all sections are open. I need to detect when user has changed between the content of one section to another.

I have to warn the user about saving your changes in a previous form because system has a timeout for inactivity, if user didnt save his data, it is lost once user gets logout from application.

the event prompt the user to save the data of the previous form.

The only way I can think of doing this is using a mouse event, but this can sometimes be a frustrating experience.

Solved

Thanks DelightedD0D, I ended up doing this

This var saves the previous form number

var PendingForm = [];


Events which fire a possible warning

//Pressing tab is reviewed if there are unsaved changes in the previous section.
$(document).keyup(function(eventTab) {
var code = eventTab.keyCode || eventTab.which;
if (code === 9) {
checkPrevious(eventTab);
}
});
//Clicking is reviewed if there are unsaved changes in the previous section
$(document).mouseup(function(eventMouse) {
checkPrevious(eventMouse);
});


Here we establish when a form is pending or has changed

$('.formSection').on('keyup change', 'input, select, textarea', function() {
$(this).closest('.formSection').addClass('pending');
//We get the number of the form by the id
var numForm = $(this).closest('.formSection').attr('id').substr(11, 2);
if ( $.inArray(numForm , pendingForm) == -1 ) {
pendingForm.push(numForm);
}
});

$('.save').click(function() {
// save data...
$(this).closest('.formSection').removeClass('pending');
pendingForm.pop();
});


And this function checks when user move to another form

function checkPrevious(e) {
var $target = $(e.target);
//If it is clicked somewhere within a form or a button that opens a section
if ($target.closest('.formSection').length || $target.hasClass('btnSection')) {
var isDisabled = false;
if (pendingForm.length > 0) {
prevForm = pendingForm.slice(-1).pop();
//Every submit form button has an id with a consecutive number in my case
//If disabled, that means it has errors in validation (Bootstrap)
//**I have to improve this verification yet**
isDisabled = $('#submitForm' + prevForm).is(':disabled');
}

// get any forms with changes that are not the current form or do not contain the clicked element
var $otherFormsWithChanges = $('.pending').filter(function() {
var $this = $(this);
return $this.hasClass('pending') && (!$this.is($target) || $this.has($target).length != 0);
});
// if another form has a change, thow up a message
// allow the user to go back to the form or ignore the changes
if ($otherFormsWithChanges.length > 0 && $otherFormsWithChanges.has($target).length === 0 ) {
var modalPrev = $('#modalPrev');
if ( isDisabled == false ) {
//If everything is ok, we can save
modalPrev.find('.modal-content .modal-body').html("<p>You have unsaved changes. Do you want to save previous form?</p>");
modalPrev.find(".btnSave").removeClass('hide');
} else {
modalPrev.find('.modal-content .modal-body').html("<p>You have some errors in your previous form.</p>");
modalPrev.find(".btnSave").addClass('hide');
}
$('#modalPrev').modal('show');
}
}
}


I show a modal instead of an alert with two buttons [Save] and [Cancel], every one has a function which do a save or close but both remove the previous section from the array to not be considered anymore.

Answer

You could do this by setting a class on the form when input values change then listening for clicks on the document and checking if a form other than the one being interacted with has changes. If one does, present a message to the user.

This should work:

Note that you dont have to use forms, just add the track-changes class to some parent of the inputs you have grouped together

jsFiddle example

$(document).mouseup(function(e) { 
  var $target = $(e.target);   
  // get any forms with changes that are not the current form or do not contain the clicked element
  var $otherFormsWithChanges = $('.pending').filter(function() {
    var $this=$(this); 
    return $this.hasClass('pending') && (!$this.is($target) || $this.has($target).length !=0);
  }); 
  // if another form has a change, thow up a message
  // allow the user to go back to the form or ignore the changes
  if ($otherFormsWithChanges.length > 0 && $otherFormsWithChanges.has($target).length===0 ) { 
    var c = confirm("You have unsaved changes.\n\n Click cancel to go back to the unsaved form or OK to ignore");
    c ? $otherFormsWithChanges.removeClass('pending') : $otherFormsWithChanges.find('input, select, textarea').focus();  
  }
});

$('.track-changes').on('keyup change', 'input, select, textarea', function() {
  $(this).closest('.track-changes').addClass('pending'); 
});

$('.save').click(function() {
  // save data...
  $(this).closest('.track-changes').removeClass('pending'); 
});
form{
  padding:10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h4>form 1</h4>
<form class="track-changes" has-changes="false" action="">
  <input type="text">
  some text
  <br>
  <button type="button" class="save">Save</button>
</form>

<h4>form 2</h4>
<form class="track-changes" has-changes="false" action="">
  <input type="text">
  some text
  <br>
  <button type="button" class="save">Save</button>
</form>

<h4>form 3</h4>
<form class="track-changes" has-changes="false" action="">
  <input type="text">
  some text
  <br>
  <button type="button" class="save">Save</button>
</form>