jQuery: dealing with multiple keypress listeners? - javascript

I have a page that needs to do two things at once:
Listen all the time for input from a scanner (which presents as keyboard input), and notice when a string is entered in the right format.
Listen for a user focussing on a particular dropdown, and typing a set of initials - when a set of initials is entered that matches the title attribute of an item in the dropdown, focus on that dropdown.
I can do either of these things separately, but not together. Code:
// Listen for input when userlist is in focus.
$("#userlist").keypress(function (e) {
initials += String.fromCharCode(e.which).toUpperCase();
$(this).find("option").filter(function () {
return $(this).attr("title").toUpperCase().indexOf(initials) === 0;
}).first().attr("selected", true);
// uses timer to check for time between keypresses
return false;
});
// Listen for scanner input all the time.
var input = '',
r1 = /^~{1}$/,
r2 = /^~{1}\d+$/,
r3 = /^~{1}\d+\.$/,
r4 = /^~{1}\d+\.\d+$/,
r5 = /^~{1}\d+\.\d+~{1}$/;
$(window).keypress(function(e) {
// when input matches final regex, do something
}
If I have both, then while the user is focussed on the dropdown, the page does not 'hear' the input from the scanner.
How can I combine the two together to make sure the page reacts to scanner input, even while the user is focussed on the dropdown?

It's because you are overriding the listener on the window object with a listener on the keypress object. I would do something like this:
var input = '',
r1 = /^~{1}$/,
r2 = /^~{1}\d+$/,
r3 = /^~{1}\d+\.$/,
r4 = /^~{1}\d+\.\d+$/,
r5 = /^~{1}\d+\.\d+~{1}$/;
function checkRegex(e) { /* Check */ }
// Listen for input when userlist is in focus.
$("#userlist").keypress(function (e) {
checkRegex(e);
initials += String.fromCharCode(e.which).toUpperCase();
$(this).find("option").filter(function () {
return $(this).attr("title").toUpperCase().indexOf(initials) === 0;
}).first().attr("selected", true);
// uses timer to check for time between keypresses
return false;
});
// Listen for scanner input all the time.
$(window).keypress(function(e) {
checkRegex(e);
}

Wouldn't delegate give you the necessary control? You could then check for the event target and respond accordingly?
ie:
$(window).delegate('keypress', function(e){
if ($(e.target).attr('id') == 'userlist'){
// something
}else{
//something else
}
});

You don't need two handlers. Just have a single handler at the window level and then check which element raised the event:
$(window).keypress(function(e) {
var $target = $(e.target);
if ($target.is("#userlist")) {
initials += String.fromCharCode(e.which).toUpperCase();
$(this).find("option").filter(function () {
return $(this).attr("title").toUpperCase().indexOf(initials) === 0;
}).first().attr("selected", true);
// uses timer to check for time between keypresses
return false;
} else {
// when input matches final regex, do something
}
});

This is probably way more complex than you'd like it to be, but I think it'll fit your purpose.
I tried to make it in the style of a jQuery plugin, and allow you to attach it to any specific object (and customize of it should override bubbling up through the DOM (in the case of your combo box) in addition to allow for windows, etc.
Anyways, try it out and see what you think. I can make modifications if necessary, just need to know what they are.
Working Example: http://www.jsfiddle.net/bradchristie/xSMQd/4/
;(function($){
$.keyListener = function(sel, options){
// avoid scope issues by using base instead of this
var base = this;
// Setup jQuery DOM elements
base.$sel = $(sel);
base.sel = sel;
base.keyPresses = '';
base.validater = null;
// add a reverse reference to the DOM object
base.$sel.data('keyListener', base);
// create an initialization function we can call
base.init = function(){
base.opts = $.extend({}, $.keyListener.defaultOptions, options);
base.$sel.keypress(function(e){
base.keyPresses += String.fromCharCode(e.which);
if (base.validator != null)
clearTimeout(base.validator);
if (base.keyPresses != '')
base.validator = setTimeout(base.validateInput, base.opts.callbackDelay);
if (base.opts.preventDefault)
e.preventDefault();
else if (base.opts.stopPropagation)
e.stopPropagation();
});
};
base.validateInput = function(){
var filter = base.opts.filter;
var reCompare = (typeof(filter)=='object'
? filter.constructor.toString().match(/regexp/i)!==null
: false);
// exception when the input is cleared out
var input = base.sel.constructor.toString().match(/HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement/i);
if (input && (!base.opts.preventDefault && base.$sel.val() == ''))
base.keyPresses = '';
// regular expression match
if (reCompare){
if (base.keyPresses.match(filter))
base.validateSuccess();
else
base.validateFailure();
// traditional string match
}else if (typeof(filter)=='string'){
if (base.keyPresses==filter)
base.validateSuccess();
else
base.validateFailure();
}
// reset string
base.keyPresses = '';
};
base.validateSuccess = function(){
if (typeof(base.opts.success)=='function')
base.opts.success(base.keyPresses);
};
base.validateFailure = function(){
if (typeof(base.opts.failure)=='function')
base.opts.failure(base.keyPresses);
};
// run the initializer
base.init();
};
$.keyListener.defaultOptions = {
// time to wait before triggering callback
// Give it time to accumulate the key presses and send it off
// as a compiled package
callbackDelay: 1000,
// Filter to apply to the input (can be a string match or a regular expression)
filter: /.*/,
// functions to callback when a match has or hasn't been made
success: function(i){},
failure: function(i){},
// would you like this to completely override key input?
preventDefault: false,
// stop it from going up the DOM tree (first object to grab the keypress
// gets it)
stopPropagation: true,
};
$.fn.extend({
keyListener: function(options){
// use return to allow jQuery to chain methods
return this.each(function(){
(new $.keyListener(this, options));
});
}
});
})(jQuery);
$('#listen-scanner,#listen-combo,#listen-text').add(window).keyListener({
filter: /^\d+$/,
success: function(input){
$('#output-scanner').text('Match!: '+input);
},
failure: function(input){
$('#output-scanner').text('No Match: '+input);
},
stopPropagation: true
});
And the HTML I tried it on:
<input type="text" id="listen-scanner" /><span id="output-scanner"></span><br />
<select id="listen-combo">
<option value="AA">Aardvarc</option>
<option value="AB">Abracabra</option>
<option value="AC">Accelerate</option>
<option value="AD">Adult</option>
</select><span id="output-combo"></span>
<textarea id="listen-text"></textarea>

Related

Filter after the submission of Input Field submitted

I have Input field and Submit Button....When i enter some text to input field it started filtering before i submit (Enter the Submit Button). How can i correct that.
I want to filter after I click the submit button.
$(function() {
var $grid = $('#container');
$grid.isotope({itemSelector: '.item'});
var filters = []; // A convenient bucket for all the filter options,
// just so we don't have to look them up in the DOM every time.
// (a global array is maybe sort of not the most elegant
// way you could deal with this but you get the idea.)
// Search event handlers
$('.quicksearch').on('keyup', function() {
// debounce removed for brevity, but you'd put it here
filters[0] = this.value;
runFilter();
});
$('#filter-select').on('change', function() {
filters[1] = this.value;
runFilter();
});
// and so on if more filters needed
// The filter itself
var runFilter = function() {
$grid.isotope({
filter: function() {
if (filters[0]) {
// at least some search text was entered:
var qsRegex = new RegExp(filters[0], 'gi');
// if the title doesn't match, eliminate it:
if (!$(this).find('.content-title').text().match(qsRegex)) {
return false;
}
}
if (filters[1]) {
// a category was selected; filter out others:
if (!($(this).hasClass(filters[1]))) {
return false;
}
}
// etcetera, for any other filters
// successfully passed all conditions, so:
return true;
}
});
}
});
In your code you have a keyup event. This event type means that every time a key on the keyboard is pressed (and released -- the keyup part), a function will call.
Your code:
// Search event handlers
$('.quicksearch').on('keyup', function() {
// debounce removed for brevity, but you'd put it here
filters[0] = this.value;
runFilter();
});
$('.quicksearch') means that when an element with the class quicksearch...
.on means when an event happens
'keyup' means when a keyboard key is pushed and released
So you have "When the user has the quicksearch selected and they type a letter then run the function". You need to change that to "when the user clicks the button then run the function".
// Search event handlers when button is pushed
$('#id-of-your-button').on('click', function() {
filters[0] = this.value;
runFilter();
});
You can use the ID of the button or the class
if you want to also be able to hit enter
You can look for the keyup on the enter key (similar to what you were doing above). And you can just add both of those blocks to your code.
You need to pass the event information to your function by using function(e). And then you can do a conditional check to see if the key that was pushed and released was the "enter" key -- which is 13 --> if(e.key === 13)
// Search when someone pushes enter in the text field
$(".quicksearch").keyup(function(e){
// Check if the enter key was hit
if(e.key === 13) {
filters[0] = this.value;
runFilter();
}
});
// Search event handlers when button is pushed
$('#id-of-your-button').on('click', function() {
filters[0] = this.value;
runFilter();
});
update based on codepen code
There were a couple of errors in your codepen code. You try to apply the filter, but you don't grab the search box's value to apply. And then you forget to pass that search parameter along to the isotope function.
You can fix this by replacing your quicksearch action to:
// use value of search field to filter
var $quicksearch = $('.bttn').on( 'click',function() {
qsRegex = new RegExp( document.getElementById('quicksearch').value, 'gi' );
$grid.isotope(qsRegex);
});
In your code you use $quicksearch.val(), but you assign the variable $quicksearch as the button.
The complete (corrected) code for the javascript in your codepen is as follows:
// quick search regex
var qsRegex;
var buttonFilter;
// init Isotope
var $grid = $('.grid').isotope({
itemSelector: '.element-item',
layoutMode: 'fitRows',
filter: function() {
var $this = $(this);
var searchResult = qsRegex ? $this.text().match( qsRegex ) : true;
var buttonResult = buttonFilter ? $this.is( buttonFilter ) : true;
return searchResult && buttonResult;
}
});
// bind filter on select change
$('.filters-select').on( 'change', function() {
// get filter value from option value
// var filterValue = this.value;
// use filterFn if matches value
buttonFilter = this.value;
//$grid.isotope({ filter: filterValue });
$grid.isotope();
});
// bind filter on select change
$('.filters-select2').on( 'change', function() {
// get filter value from option value
// var filterValue = this.value;
// use filterFn if matches value
buttonFilter = this.value;
//$grid.isotope({ filter: filterValue });
$grid.isotope();
});
// use value of search field to filter
var $quicksearch = $('.bttn').on( 'click',function() {
qsRegex = new RegExp( document.getElementById('quicksearch').value, 'gi' );
$grid.isotope(qsRegex);
});
// Search when someone pushes enter in the text field
$("#quicksearch").keyup(function(e){
if(e.key === 16 || e.key === 13 || e.key === 'Enter') {
qsRegex = new RegExp( document.getElementById('quicksearch').value, 'gi' );
$grid.isotope(qsRegex);
}
});

Invoke a function after right click paste in jQuery

I know we can use bind paste event as below:
$('#id').bind('paste', function(e) {
alert('pasting!')
});
But the problem is, that it will call before the pasted text paste. I want a function to be triggered after the right click -> paste text pasted on the input field, so that I can access the pasted value inside the event handler function.
.change() event also doesn't help. Currently I use .keyup() event, because I need to show the remaining characters count while typing in that input field.
Kind of a hack, but:
$("#id").bind('paste', function(e) {
var ctl = $(this);
setTimeout(function() {
//Do whatever you want to $(ctl) here....
}, 100);
});
Why not use the "input" event?
$("#id").bind('input', function(e) {
var $this = $(this);
console.log($this.val());
});
This will stop user from any pasting, coping or cutting with the keyboard:
$("#myField").keydown(function(event) {
var forbiddenKeys = new Array('c', 'x', 'v');
var keyCode = (event.keyCode) ? event.keyCode : event.which;
var isCtrl;
isCtrl = event.ctrlKey
if (isCtrl) {
for (i = 0; i < forbiddenKeys.length; i++) {
if (forbiddenKeys[i] == String.fromCharCode(keyCode).toLowerCase()) {
return false;
}
}
}
return true;
});
This one will do the same for the mouse events:
$("#myField").bind("cut copy paste",function(event) {
event.preventDefault();
});
Even though the above one will not prevent right clicks, the user will not be able to paste, cut or copy from that field.
To use it after the event, like you wondered on your question, you must use JavaScript Timing Event
setTimeout(function() {
// your code goes here
}, 10);
I had the same issue, I opted to replicate the paste action through javascript and use that output instead:
var getPostPasteText = function (element, pastedData) {
// get the highlighted text (if any) from the element
var selection = getSelection(element);
var selectionStart = selection.start;
var selectionEnd = selection.end;
// figure out what text is to the left and right of the highlighted text (if any)
var oldText = $(element).val();
var leftPiece = oldText.substr(0, selectionStart);
var rightPiece = oldText.substr(selectionEnd, oldText.length);
// compute what the new value of the element will be after the paste
// note behavior of paste is to REPLACE any highlighted text
return leftPiece + pastedData + rightPiece;
};
See IE's document.selection.createRange doesn't include leading or trailing blank lines for source of the getSelection function.
No need to bind :
$(document).on('keyup input', '#myID', function () {
//Do something
});

Warn user before leaving web page with unsaved changes

I have some pages with forms in my application.
How can I secure the form in such a way that if someone navigates away or closes the browser tab, they should be prompted to to confirm they really want to leave the form with unsaved data?
Short, wrong answer:
You can do this by handling the beforeunload event and returning a non-null string:
window.addEventListener("beforeunload", function (e) {
var confirmationMessage = 'It looks like you have been editing something. '
+ 'If you leave before saving, your changes will be lost.';
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
});
The problem with this approach is that submitting a form is also firing the unload event. This is fixed easily by adding the a flag that you're submitting a form:
var formSubmitting = false;
var setFormSubmitting = function() { formSubmitting = true; };
window.onload = function() {
window.addEventListener("beforeunload", function (e) {
if (formSubmitting) {
return undefined;
}
var confirmationMessage = 'It looks like you have been editing something. '
+ 'If you leave before saving, your changes will be lost.';
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
});
};
Then calling the setter when submitting:
<form method="post" onsubmit="setFormSubmitting()">
<input type="submit" />
</form>
But read on...
Long, correct answer:
You also don't want to show this message when the user hasn't changed anything on your forms. One solution is to use the beforeunload event in combination with a "dirty" flag, which only triggers the prompt if it's really relevant.
var isDirty = function() { return false; }
window.onload = function() {
window.addEventListener("beforeunload", function (e) {
if (formSubmitting || !isDirty()) {
return undefined;
}
var confirmationMessage = 'It looks like you have been editing something. '
+ 'If you leave before saving, your changes will be lost.';
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
});
};
Now to implement the isDirty method, there are various approaches.
You can use jQuery and form serialization, but this approach has some flaws. First you have to alter the code to work on any form ($("form").each() will do), but the greatest problem is that jQuery's serialize() will only work on named, non-disabled elements, so changing any disabled or unnamed element will not trigger the dirty flag. There are workarounds for that, like making controls readonly instead of enabling, serializing and then disabling the controls again.
So events seem the way to go. You can try listening for keypresses. This event has a few issues:
Won't trigger on checkboxes, radio buttons, or other elements that are being altered through mouse input.
Will trigger for irrelevant keypresses like the Ctrl key.
Won't trigger on values set through JavaScript code.
Won't trigger on cutting or pasting text through context menus.
Won't work for virtual inputs like datepickers or checkbox/radiobutton beautifiers which save their value in a hidden input through JavaScript.
The change event also doesn't trigger on values set from JavaScript code, so also won't work for virtual inputs.
Binding the input event to all inputs (and textareas and selects) on your page won't work on older browsers and, like all event handling solutions mentioned above, doesn't support undo. When a user changes a textbox and then undoes that, or checks and unchecks a checkbox, the form is still considered dirty.
And when you want to implement more behavior, like ignoring certain elements, you'll have even more work to do.
Don't reinvent the wheel:
So before you think about implementing those solutions and all required workarounds, realize you're reinventing the wheel and you're prone to running into problems others have already solved for you.
If your application already uses jQuery, you may as well use tested, maintained code instead of rolling your own, and use a third-party library for all of this.
jquery.dirty (suggested by #troseman in the comments) provides functions for properly detecting whether a form has been changed or not, and preventing the user from leaving the page while displaying a prompt. It also has other useful functions like resetting the form, and setting the current state of the form as the "clean" state. Example usage:
$("#myForm").dirty({preventLeaving: true});
An older, currently abandoned project, is jQuery's Are You Sure? plugin, which also works great; see their demo page. Example usage:
<script src="jquery.are-you-sure.js"></script>
<script>
$(function() {
$('#myForm').areYouSure(
{
message: 'It looks like you have been editing something. '
+ 'If you leave before saving, your changes will be lost.'
}
);
});
</script>
Custom messages not supported everywhere
Do note that since 2011 already, Firefox 4 didn't support custom messages in this dialog. As of april 2016, Chrome 51 is being rolled out in which custom messages are also being removed.
Some alternatives exist elsewhere on this site, but I think a dialog like this is clear enough:
Do you want to leave this site?
Changes you made may not be saved.
Leave Stay
Check out the JavaScript onbeforeunload event. It's non-standard JavaScript introduced by Microsoft, however it works in most browsers and their onbeforeunload documentation has more information and examples.
Universal solution requiring no configuration that automatically detects all input modification, including contenteditable elements:
"use strict";
(() => {
const modified_inputs = new Set;
const defaultValue = "defaultValue";
// store default values
addEventListener("beforeinput", (evt) => {
const target = evt.target;
if (!(defaultValue in target || defaultValue in target.dataset)) {
target.dataset[defaultValue] = ("" + (target.value || target.textContent)).trim();
}
});
// detect input modifications
addEventListener("input", (evt) => {
const target = evt.target;
let original;
if (defaultValue in target) {
original = target[defaultValue];
} else {
original = target.dataset[defaultValue];
}
if (original !== ("" + (target.value || target.textContent)).trim()) {
if (!modified_inputs.has(target)) {
modified_inputs.add(target);
}
} else if (modified_inputs.has(target)) {
modified_inputs.delete(target);
}
});
// clear modified inputs upon form submission
addEventListener("submit", (evt) => {
modified_inputs.clear();
// to prevent the warning from happening, it is advisable
// that you clear your form controls back to their default
// state with evt.target.reset() or form.reset() after submission
});
// warn before closing if any inputs are modified
addEventListener("beforeunload", (evt) => {
if (modified_inputs.size) {
const unsaved_changes_warning = "Changes you made may not be saved.";
evt.returnValue = unsaved_changes_warning;
return unsaved_changes_warning;
}
});
})();
via jquery
$('#form').data('serialize',$('#form').serialize()); // On load save form current state
$(window).bind('beforeunload', function(e){
if($('#form').serialize()!=$('#form').data('serialize'))return true;
else e=null; // i.e; if form state change show warning box, else don't show it.
});
You can Google JQuery Form Serialize function, this will collect all form inputs and save it in array. I guess this explain is enough :)
Built on top of Wasim A.'s excellent idea to use serialization. The problem there was that the warning was also shown when the form was being submitted. This has been fixed here.
var isSubmitting = false
$(document).ready(function () {
$('form').submit(function(){
isSubmitting = true
})
$('form').data('initial-state', $('form').serialize());
$(window).on('beforeunload', function() {
if (!isSubmitting && $('form').serialize() != $('form').data('initial-state')){
return 'You have unsaved changes which will not be saved.'
}
});
})
It has been tested in Chrome and IE 11.
Based on the previous answers, and cobbled together from various places in stack overflow, here is the solution I came up with which handles the case when you actually want to submit your changes:
window.thisPage = window.thisPage || {};
window.thisPage.isDirty = false;
window.thisPage.closeEditorWarning = function (event) {
if (window.thisPage.isDirty)
return 'It looks like you have been editing something' +
' - if you leave before saving, then your changes will be lost.'
else
return undefined;
};
$("form").on('keyup', 'textarea', // You can use input[type=text] here as well.
function () {
window.thisPage.isDirty = true;
});
$("form").submit(function () {
QC.thisPage.isDirty = false;
});
window.onbeforeunload = window.thisPage.closeEditorWarning;
It's worth noting that IE11 seems to require that the closeEditorWarning function returns undefined for it not to show an alert.
The following one-liner has worked for me.
window.onbeforeunload = s => modified ? "" : null;
Just set modified to true or false depending on the state of your application.
You can use serialize() to create a URL encoded text string by serializing form values and check whether the form has changed beforeunload
$(document).ready(function(){
var form = $('#some-form'),
original = form.serialize()
form.submit(function(){
window.onbeforeunload = null
})
window.onbeforeunload = function(){
if (form.serialize() != original)
return 'Are you sure you want to leave?'
}
})
Refer this link https://coderwall.com/p/gny70a/alert-when-leaving-page-with-unsaved-form
Written by Vladimir Sidorenko
Following code works great. You need to reach your form elements' input changes via id attribute:
var somethingChanged=false;
$('#managerForm input').change(function() {
somethingChanged = true;
});
$(window).bind('beforeunload', function(e){
if(somethingChanged)
return "You made some changes and it's not saved?";
else
e=null; // i.e; if form state change show warning box, else don't show it.
});
});
Tested Eli Grey's universal solution, only worked after I simplified the code to
'use strict';
(() => {
const modified_inputs = new Set();
const defaultValue = 'defaultValue';
// store default values
addEventListener('beforeinput', evt => {
const target = evt.target;
if (!(defaultValue in target.dataset)) {
target.dataset[defaultValue] = ('' + (target.value || target.textContent)).trim();
}
});
// detect input modifications
addEventListener('input', evt => {
const target = evt.target;
let original = target.dataset[defaultValue];
let current = ('' + (target.value || target.textContent)).trim();
if (original !== current) {
if (!modified_inputs.has(target)) {
modified_inputs.add(target);
}
} else if (modified_inputs.has(target)) {
modified_inputs.delete(target);
}
});
addEventListener(
'saved',
function(e) {
modified_inputs.clear()
},
false
);
addEventListener('beforeunload', evt => {
if (modified_inputs.size) {
const unsaved_changes_warning = 'Changes you made may not be saved.';
evt.returnValue = unsaved_changes_warning;
return unsaved_changes_warning;
}
});
})();
The modifications to his is deleted the usage of target[defaultValue] and only use target.dataset[defaultValue] to store the real default value.
And I added a 'saved' event listener where the 'saved' event will be triggered by yourself on your saving action succeeded.
But this 'universal' solution only works in browsers, not works in app's webview, for example, wechat browsers.
To make it work in wechat browsers(partially) also, another improvements again:
'use strict';
(() => {
const modified_inputs = new Set();
const defaultValue = 'defaultValue';
// store default values
addEventListener('beforeinput', evt => {
const target = evt.target;
if (!(defaultValue in target.dataset)) {
target.dataset[defaultValue] = ('' + (target.value || target.textContent)).trim();
}
});
// detect input modifications
addEventListener('input', evt => {
const target = evt.target;
let original = target.dataset[defaultValue];
let current = ('' + (target.value || target.textContent)).trim();
if (original !== current) {
if (!modified_inputs.has(target)) {
modified_inputs.add(target);
}
} else if (modified_inputs.has(target)) {
modified_inputs.delete(target);
}
if(modified_inputs.size){
const event = new Event('needSave')
window.dispatchEvent(event);
}
});
addEventListener(
'saved',
function(e) {
modified_inputs.clear()
},
false
);
addEventListener('beforeunload', evt => {
if (modified_inputs.size) {
const unsaved_changes_warning = 'Changes you made may not be saved.';
evt.returnValue = unsaved_changes_warning;
return unsaved_changes_warning;
}
});
const ua = navigator.userAgent.toLowerCase();
if(/MicroMessenger/i.test(ua)) {
let pushed = false
addEventListener('needSave', evt => {
if(!pushed) {
pushHistory();
window.addEventListener("popstate", function(e) {
if(modified_inputs.size) {
var cfi = confirm('确定要离开当前页面嘛?' + JSON.stringify(e));
if (cfi) {
modified_inputs.clear()
history.go(-1)
}else{
e.preventDefault();
e.stopPropagation();
}
}
}, false);
}
pushed = true
});
}
function pushHistory() {
var state = {
title: document.title,
url: "#flag"
};
window.history.pushState(state, document.title, "#flag");
}
})();
var unsaved = false;
$(":input").change(function () {
unsaved = true;
});
function unloadPage() {
if (unsaved) {
alert("You have unsaved changes on this page. Do you want to leave this page and discard your changes or stay on this page?");
}
}
window.onbeforeunload = unloadPage;
Short answer:
let pageModified = true
window.addEventListener("beforeunload",
() => pageModified ? 'Close page without saving data?' : null
)
The solution by Eerik Sven Puudist ...
var isSubmitting = false;
$(document).ready(function () {
$('form').submit(function(){
isSubmitting = true
})
$('form').data('initial-state', $('form').serialize());
$(window).on('beforeunload', function() {
if (!isSubmitting && $('form').serialize() != $('form').data('initial-state')){
return 'You have unsaved changes which will not be saved.'
}
});
})
... spontaneously did the job for me in a complex object-oriented setting without any changes necessary.
The only change I applied was to refer to the concrete form (only one form per file) called "formForm" ('form' -> '#formForm'):
<form ... id="formForm" name="formForm" ...>
Especially well done is the fact that the submit button is being "left alone".
Additionally, it works for me also with the lastest version of Firefox (as of February 7th, 2019).
Adding to te idea of #codecaster
you could add this to every page with a form (in my case i use it in global way so only on forms would have this warn) change his function to
if ( formSubmitting || document.getElementsByTagName('form').length == 0)
Also put on forms submit including login and in cancel buttons links so when person press cancel or submit the form won't trigger the warn also in every page witouth a form...
<a class="btn btn-danger btn-md" href="back/url" onclick="setFormSubmitting()">Cancel</a>
You could check for a detailed explanation here:
http://techinvestigations.redexp.in/comparison-of-form-values-on-load-and-before-close/
The main code:
function formCompare(defaultValues, valuesOnClose) {
// Create arrays of property names
var aPropsFormLoad = Object.keys(defaultValues);
var aPropsFormClose = Object.keys(valuesOnClose);
// If number of properties is different,
// objects are not equivalent
if (aPropsFormLoad.length != aPropsFormClose.length) {
return false;
}
for (var i = 0; i < aPropsFormLoad.length; i++) {
var propName = aPropsFormLoad[i];
// If values of same property are not equal,
// objects are not equivalent
if (defaultValues[aPropsFormLoad]+"" !== valuesOnClose[aPropsFormLoad]+"") {
return false;
}
}
// If we made it this far, objects
// are considered equivalent
return true;
}
//add polyfill for older browsers, as explained on the link above
//use the block below on load
for(i=0; i < document.forms[0].elements.length; i++){
console.log("The field name is: " + document.forms[0].elements[i].name +
" and it’s value is: " + document.forms[0].elements[i].value );
aPropsFormLoad[i] = document.forms[0].elements[i].value;
}
//create a similar array on window unload event.
//and call the utility function
if (!formCompare(aPropsOnLoad, aPropsOnClose))
{
//perform action:
//ask user for confirmation or
//display message about changes made
}
I did it differently, sharing here so that someone can get help, tested only with Chrome.
I wanted to warn user before closing the tab only if there are some changes.
<input type="text" name="field" value="" class="onchange" />
var ischanged = false;
$('.onchange').change(function () {
ischanged = true;
});
window.onbeforeunload = function (e) {
if (ischanged) {
return "Make sure to save all changes.";
}
};
Works good, but got an-other issue, when i submit the form i get the unwanted warning, i saw lots of workaround on it, this is because onbeforeunload fires before onsubmit thats why we can't handle it in onsubmit event like onbeforeunload = null, but onclick event of submit button fires before these both events, so i updated the code
var isChanged = false;
var isSubmit = false;
window.onbeforeunload = function (e) {
if (isChanged && (!isSubmit)) {
return "Make sure to save all changes.";
}
};
$('#submitbutton').click(function () {
isSubmit = true;
});
$('.onchange').change(function () {
isChanged = true;
});
I made following code. It can compare changes in all fields (except those marked with .ignoreDirty class) or optionally for currently visible fields only. It can be reinitialized for new fields added by Javascript. From that reason I save not the form status but the status of each control.
/* Dirty warning for forms */
dirty = (skipHiddenOrNullToInit) => {
/* will return True if there are changes in form(s)
for first initialization you can use both: .dirty(null) or .dirty() (ignore its result)
.dirty(null) will (re)initialize all controls - in addititon use it after Save if you stay on same page
.dirty() will initialize new controls - in addititon use it if you add new fields with JavaScript
then
.dirty() (or: .dirty(false)) says if data are changed without regard to hidden fields
.dirty(true) says if data are changed with regard to hidden fields (ie. fields with .d-none or .hidden class)
controls with .ignoreDirty class will be skipped always
previous about .d-none, .hidden, .ignoreDirty applies to the control itself and all its ancestors
*/
let isDirty = false;
let skipSelectors = '.ignoreDirty';
if (skipHiddenOrNullToInit) {
skipSelectors += ', .d-none, .hidden'
} else if (skipHiddenOrNullToInit === undefined) {
skipHiddenOrNullToInit = false;
}
$('input, select').each(
function(_idx, el) {
if ($(el).prop('type') !== 'hidden') {
let dirtyInit = $(el).data('dirty-init');
if (skipHiddenOrNullToInit === null || dirtyInit === undefined) {
try {
isChromeAutofillEl = $(el).is(":-webkit-autofill");
} catch (error) {
isChromeAutofillEl = false;
}
if (isChromeAutofillEl && $(el).data('dirty-init') === undefined) {
setTimeout(function() { // otherwise problem with Chrome autofilled controls
$(el).data('dirty-init', $(el).val());
}, 200)
} else {
$(el).data('dirty-init', $(el).val());
}
} else if ($(el).closest(skipSelectors).length === 0 && dirtyInit !== $(el).val()) {
isDirty = true;
return false; // breaks jQuery .each
}
}
}
);
return isDirty;
}
I have additional troubles with Chrome autofill values because it is difficult to initizialize and have them loaded already. So I do not initialize on page load but in any focusin event. (But: Maybe there is still problem with control values changed by JavaScript.) I use following code which I call at page load:
let init_dirty = (ifStayFunc) => {
/* ifStayFunc: optional callback when user decides to stay on page
use .clearDirty class to avoid warning on some button, however:
if the button fires JavaScript do't use .clearDirty class and instead
use directly dirty(null) in code - to be sure it will run before window.location */
$('input, select').on('focusin', function(evt) {
if (!$('body').data('dirty_initialized')) {
dirty();
$('body').data('dirty_initialized', true);
}
});
window.addEventListener('beforeunload', (evt) => {
if (dirty(true)) {
if (ifStayFunc) {
ifStayFunc();
}
evt.preventDefault();
evt.returnValue = ''; // at least Google Chrome requires this
}
});
$('.clearDirty').on('click', function(evt) {
dirty(null);
});
};
So, I add the .clearDirty class to the buttons which provide Save and that way I prevent the warning in this case.
Callback ifStayFunc allows me to do something if user will Stay on Page while he is warned. Typically I can show additional Save Button (if I have still visible only some default/primary button, which makes Safe+SomethingMore - and I want allow Save withou this "SomethingMore").

How can I check if a value is changed on blur event?

Basically I need to check if the value is changed in a textbox on the 'blur' event so that if the value is not changed, I want to cancel the blur event.
If it possible to check it the value is changed by user on the blur event of an input HTML element?
I don't think there is a native way to do this. What I would do is, add a function to the focus event that saves the current value into a variable attached to the element (element.oldValue = element.value). You could check against that value onBLur.
Within the onblur event, you can compare the value against the defaultValue to determine whether a change happened:
<input onblur="if(this.value!=this.defaultValue){alert('changed');}">
The defaultValue will contain the initial value of the object, whereas the value will contain the current value of the object after a change has been made.
References:
value vs defaultValue
You can't cancel the blur event, you need to refocus in a timer. You could either set up a variable onfocus or set a hasChanged variable on the change event. The blur event fires after the change event (unfortunately, for this situation) otherwise you could have just reset the timer in the onchange event.
I'd take an approach similar to this:
(function () {
var hasChanged;
var element = document.getElementById("myInputElement");
element.onchange = function () { hasChanged = true; }
element.onblur = function () {
if (hasChanged) {
alert("You need to change the value");
// blur event can't actually be cancelled so refocus using a timer
window.setTimeout(function () { element.focus(); }, 0);
}
hasChanged = false;
}
})();
Why not just maintaining a custom flag on the input element?
input.addEventListener('change', () => input.hasChanged = true);
input.addEventListener('blur', () => 
{
if (!input.hasChanged) { return; }
input.hasChanged = false;
// Do your stuff
});
https://jsfiddle.net/d7yx63aj
Using Jquery events we can do this logic
Step1 : Declare a variable to compare the value
var lastVal ="";
Step 2: On focus get the last value from form input
$("#validation-form :input").focus(function () {
lastVal = $(this).val();
});
Step3:On blur compare it
$("#validation-form :input").blur(function () {
if (lastVal != $(this).val())
alert("changed");
});
You can use this code:
var Old_Val;
var Input_Field = $('#input');
Input_Field.focus(function(){
Old_Val = Input_Field.val();
});
Input_Field.blur(function(){
var new_input_val = Input_Field.val();
if (new_input_val != Old_Val){
// execute you code here
}
});
I know this is old, but I figured I'd put this in case anyone wants an alternative. This seems ugly (at least to me) but having to deal with the way the browser handles the -1 index is what was the challenge. Yes, I know it can be done better with the jquery.data, but I'm not that familiar with that just yet.
Here is the HTML code:
<select id="selected">
<option value="1">A</option>
<option value="2">B</option>
<option value="3">C</option>
</select>
Here is the javascript code:
var currentIndex; // set up a global variable for current value
$('#selected').on(
{ "focus": function() { // when the select is clicked on
currentIndex = $('#selected').val(); // grab the current selected option and store it
$('#selected').val(-1); // set the select to nothing
}
, "change": function() { // when the select is changed
choice = $('#selected').val(); // grab what (if anything) was selected
this.blur(); // take focus away from the select
//alert(currentIndex);
//setTimeout(function() { alert(choice); }, 0);
}
, "blur": function() { // when the focus is taken from the select (handles when something is changed or not)
//alert(currentIndex);
//alert($('#selected').val());
if ($('#selected').val() == null) { // if nothing has changed (because it is still set to the -1 value, or null)
$('#selected').val(currentIndex); // set the value back to what it originally was (otherwise it will stay at what was newly selected)
} else { // if anything has changed, even if it's the same one as before
if ($('#selected').val() == 2) { // in case you want to do something when a certain option is selected (in my case, option B, or value 2)
alert('I would do something');
}
}
}
});
Something like this. Using Kevin Nadsady's above suggestion of
this.value!=this.defaultValue
I use a shared CSS class on a bunch of inputs then do:
for (var i = 0; i < myInputs.length; i++) {
myInputs[i].addEventListener('blur', function (evt) {
if(this.value!=this.defaultValue){
//value was changed now do your thing
}
});
myInputs[i].addEventListener('focus', function (evt) {
evt.target.setAttribute("value",evt.target.value);
});
}
Even if this is an old post, I thought i'd share a way to do this with simple javascript.
The javascript portion:
<script type="text/javascript">
function HideLabel(txtField){
if(txtField.name=='YOURBOXNAME'){
if(txtField.value=='YOURBOXNAME')
txtField.value = '';
else
txtField.select();
}
}
function ShowLabel(YOURBOXNAME){
if(txtField.name=='YOURBOXNAME'){
if(txtField.value.trim()=='')
txtField.value = 'YOURDEFAULTVALUE';
}
}
</script>
Now the text field in your form:
<input type="text" id="input" name="YOURBOXNAME" value="1" onfocus="HideLabel(this)"
onblur="ShowLabel(this)">
And bewn! No Jquery needed. just simple javascript. cut and paste those bad boys. (remember to put your javascript above the body in your html)
Similar to #Kevin Nadsady's post, the following will work in native JS functions and JQuery listener events. Within the onblur event, you can compare the value against the defaultValue:
$(".saveOnChange").on("blur", function () {
if (this.value != this.defaultValue) {
//Set the default value to the new value
this.defaultValue = this.value;
//todo: save changes
alert("changed");
}
});
The idea is to have a hidden field to keep the old value and whenever the onblur event happens, check the change and update the hidden value with the current text value
string html = "<input type=text id=it" + row["cod"] + "inputDesc value='"
+ row["desc"] + "' onblur =\"if (this.value != document.getElementById('hd" + row["cod"].ToString() +
"inputHiddenDesc').value){ alert('value change'); document.getElementById('hd" + row["cod"].ToString() +
"inputHiddenDesc').value = this.value; }\"> " +
"<input type=hidden id=hd" + row["cod"].ToString() + "inputHiddenDesc value='" + row["desc"] + "'>";

What is the best way to track changes in a form via javascript?

I'd like to track changes in inputs in a form via javascript. My intent is (but not limited) to
enable "save" button only when something has changed
alert if the user wants to close the page and something is not saved
Ideas?
Loop through all the input elements, and put an onchange handler on each. When that fires, set a flag which lets you know the form has changed. A basic version of that would be very easy to set up, but wouldn't be smart enough to recognize if someone changed an input from "a" to "b" and then back to "a". If it were important to catch that case, then it'd still be possible, but would take a bit more work.
Here's a basic example in jQuery:
$("#myForm")
.on("input", function() {
// do whatever you need to do when something's changed.
// perhaps set up an onExit function on the window
$('#saveButton').show();
})
;
Text form elements in JS expose a .value property and a .defaultValue property, so you can easily implement something like:
function formChanged(form) {
for (var i = 0; i < form.elements.length; i++) {
if(form.elements[i].value != form.elements[i].defaultValue) return(true);
}
return(false);
}
For checkboxes and radio buttons see whether element.checked != element.defaultChecked, and for HTML <select /> elements you'll need to loop over the select.options array and check for each option whether selected == defaultSelected.
You might want to look at using a framework like jQuery to attach handlers to the onchange event of each individual form element. These handlers can call your formChanged() code and modify the enabled property of your "save" button, and/or attach/detach an event handler for the document body's beforeunload event.
Here's a javascript & jquery method for detecting form changes that is simple. It disables the submit button until changes are made. It detects attempts to leave the page by means other than submitting the form. It accounts for "undos" by the user, it is encapsulated within a function for ease of application, and it doesn't misfire on submit. Just call the function and pass the ID of your form.
This function serializes the form once when the page is loaded, and again before the user leaves the page. If the two form states are different, the prompt is shown.
Try it out: http://jsfiddle.net/skibulk/ev5rE/
function formUnloadPrompt(formSelector) {
var formA = $(formSelector).serialize(), formB, formSubmit = false;
// Detect Form Submit
$(formSelector).submit( function(){
formSubmit = true;
});
// Handle Form Unload
window.onbeforeunload = function(){
if (formSubmit) return;
formB = $(formSelector).serialize();
if (formA != formB) return "Your changes have not been saved.";
};
// Enable & Disable Submit Button
var formToggleSubmit = function(){
formB = $(formSelector).serialize();
$(formSelector+' [type="submit"]').attr( "disabled", formA == formB);
};
formToggleSubmit();
$(formSelector).change(formToggleSubmit);
$(formSelector).keyup(formToggleSubmit);
}
// Call function on DOM Ready:
$(function(){
formUnloadPrompt('form');
});
Try
function isModifiedForm(form){
var __clone = $(form).clone();
__clone[0].reset();
return $(form).serialize() == $(__clone).serialize();
}
Hope its helps ))
If your using a web app framework (rails, ASP.NET, Cake, symfony), there should be packages for ajax validation,
http://webtecker.com/2008/03/17/list-of-ajax-form-validators/
and some wrapper on onbeforeunload() to warn users taht are about to close the form:
http://pragmatig.wordpress.com/2008/03/03/protecting-userdata-from-beeing-lost-with-jquery/
Detecting Unsaved Changes
I answered a question like this on Ars Technica, but the question was framed such that the changes needed to be detected even if the user does not blur a text field (in which case the change event never fires). I came up with a comprehensive script which:
enables submit and reset buttons if field values change
disables submit and reset buttons if the form is reset
interrupts leaving the page if form data has changed and not been submitted
supports IE 6+, Firefox 2+, Safari 3+ (and presumably Opera but I did not test)
This script depends on Prototype but could be easily adapted to another library or to stand alone.
$(document).observe('dom:loaded', function(e) {
var browser = {
trident: !!document.all && !window.opera,
webkit: (!(!!document.all && !window.opera) && !document.doctype) ||
(!!window.devicePixelRatio && !!window.getMatchedCSSRules)
};
// Select form elements that won't bubble up delegated events (eg. onchange)
var inputs = $('form_id').select('select, input[type="radio"], input[type="checkbox"]');
$('form_id').observe('submit', function(e) {
// Don't bother submitting if form not modified
if(!$('form_id').hasClassName('modified')) {
e.stop();
return false;
}
$('form_id').addClassName('saving');
});
var change = function(e) {
// Paste event fires before content has been pasted
if(e && e.type && e.type == 'paste') {
arguments.callee.defer();
return false;
}
// Check if event actually results in changed data
if(!e || e.type != 'change') {
var modified = false;
$('form_id').getElements().each(function(element) {
if(element.tagName.match(/^textarea$/i)) {
if($F(element) != element.defaultValue) {
modified = true;
}
return;
} else if(element.tagName.match(/^input$/i)) {
if(element.type.match(/^(text|hidden)$/i) && $F(element) != element.defaultValue) {
modified = true;
} else if(element.type.match(/^(checkbox|radio)$/i) && element.checked != element.defaultChecked) {
modified = true;
}
}
});
if(!modified) {
return false;
}
}
// Mark form as modified
$('form_id').addClassName('modified');
// Enable submit/reset buttons
$('reset_button_id').removeAttribute('disabled');
$('submit_button_id').removeAttribute('disabled');
// Remove event handlers as they're no longer needed
if(browser.trident) {
$('form_id').stopObserving('keyup', change);
$('form_id').stopObserving('paste', change);
} else {
$('form_id').stopObserving('input', change);
}
if(browser.webkit) {
$$('#form_id textarea').invoke('stopObserving', 'keyup', change);
$$('#form_id textarea').invoke('stopObserving', 'paste', change);
}
inputs.invoke('stopObserving', 'change', arguments.callee);
};
$('form_id').observe('reset', function(e) {
// Unset form modified, restart modified check...
$('reset_button_id').writeAttribute('disabled', true);
$('submit_button_id').writeAttribute('disabled', true);
$('form_id').removeClassName('modified');
startObservers();
});
var startObservers = (function(e) {
if(browser.trident) {
$('form_id').observe('keyup', change);
$('form_id').observe('paste', change);
} else {
$('form_id').observe('input', change);
}
// Webkit apparently doesn't fire oninput in textareas
if(browser.webkit) {
$$('#form_id textarea').invoke('observe', 'keyup', change);
$$('#form_id textarea').invoke('observe', 'paste', change);
}
inputs.invoke('observe', 'change', change);
return arguments.callee;
})();
window.onbeforeunload = function(e) {
if($('form_id').hasClassName('modified') && !$('form_id').hasClassName('saving')) {
return 'You have unsaved content, would you really like to leave the page? All your changes will be lost.';
}
};
});
I would store each fields value in a variable when the page loads, then compare those values when the user unloads the page. If any differences are detected you will know what to save and better yet, be able to specifically tell the user what data will not be saved if they exit.
// this example uses the prototype library
// also, it's not very efficient, I just threw it together
var valuesAtLoad = [];
var valuesOnCheck = [];
var isDirty = false;
var names = [];
Event.observe(window, 'load', function() {
$$('.field').each(function(i) {
valuesAtLoad.push($F(i));
});
});
var checkValues = function() {
var changes = [];
valuesOnCheck = [];
$$('.field').each(function(i) {
valuesOnCheck.push($F(i));
});
for(var i = 0; i <= valuesOnCheck.length - 1; i++ ) {
var source = valuesOnCheck[i];
var compare = valuesAtLoad[i];
if( source !== compare ) {
changes.push($$('.field')[i]);
}
}
return changes.length > 0 ? changes : [];
};
setInterval(function() { names = checkValues().pluck('id'); isDirty = names.length > 0; }, 100);
// notify the user when they exit
Event.observe(window, 'beforeunload', function(e) {
e.returnValue = isDirty ? "you have changed the following fields: \r\n" + names + "\r\n these changes will be lost if you exit. Are you sure you want to continue?" : true;
});
I've used dirtyforms.js. Works well for me.
http://mal.co.nz/code/jquery-dirty-forms/
To alert the user before closing, use unbeforeunload:
window.onbeforeunload = function() {
return "You are about to lose your form data.";
};
I did some Cross Browser Testing.
On Chrome and Safari this is nice:
<form onchange="validate()">
...
</form>
For Firefox + Chrome/Safari I go with this:
<form onkeydown="validate()">
...
<input type="checkbox" onchange="validate()">
</form>
Items like checkboxes or radiobuttons need an own onchange event listener.
Attach an event handler to each form input/select/textarea's onchange event. Setting a variable to tell you if you should enable the "save" button. Create an onunload hander that checks for a dirty form too, and when the form is submitted reset the variable:
window.onunload = checkUnsavedPage;
var isDirty = false;
var formElements = //Get a reference to all form elements
for(var i = 0; len = formElements.length; i++) {
//Add onchange event to each element to call formChanged()
}
function formChanged(event) {
isDirty = false;
document.getElementById("savebtn").disabled = "";
}
function checkUnsavedPage() {
if (isDirty) {
var isSure = confirm("you sure?");
if (!isSure) {
event.preventDefault();
}
}
}
Here's a full implementation of Dylan Beattie's suggestion:
Client/JS Framework for "Unsaved Data" Protection?
You shouldn't need to store initial values to determine if the form has changed, unless you're populating it dynamically on the client side (although, even then, you could still set up the default properties on the form elements).
You can also check out this jQuery plugin I built at jQuery track changes in forms plugin
See the demo here and download the JS here
If you are open to using jQuery, see my answer a similar question:
Disable submit button unless original form data has changed.
I had the same challenge and i was thinking of a common solution. The code below is not perfect, its from initial r&d. Following are the steps I used:
1) Move the following JS to a another file (say changeFramework.js)
2) Include it in your project by importing it
3) In your html page, whichever control needs monitoring, add the class "monitorChange"
4) The global variable 'hasChanged' will tell, if there is any change in the page you working on.
<script type="text/javascript" id="MonitorChangeFramework">
// MONITOR CHANGE FRAMEWORK
// ALL ELEMENTS WITH CLASS ".monitorChange" WILL BE REGISTERED FOR CHANGE
// ON CHANGE IT WILL RAISE A FLAG
var hasChanged;
function MonitorChange() {
hasChanged = false;
$(".monitorChange").change(function () {
hasChanged = true;
});
}
Following are the controls where I used this framework:
<textarea class="monitorChange" rows="5" cols="10" id="testArea"></textarea></br>
<div id="divDrinks">
<input type="checkbox" class="chb monitorChange" value="Tea" />Tea </br>
<input type="checkbox" class="chb monitorChange" value="Milk" checked='checked' />Milk</br>
<input type="checkbox" class="chb monitorChange" value="Coffee" />Coffee </br>
</div>
<select id="comboCar" class="monitorChange">
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
<option value="mercedes">Mercedes</option>
<option value="audi">Audi</option>
</select>
<button id="testButton">
test</button><a onclick="NavigateTo()">next >>> </a>
I believe there can be huge improvement in this framework. Comment/Changes/feedbacks are welcome. :)

Categories

Resources