select multiple rows using control and shift key - javascript

Demo
<table id="tableStudent" border="1">
<thead>
<tr><th>ID</th><th>Name</th> <th>Class</th></tr>
</thead>
<tbody>
<tr><td>1</td><td>John</td><td>4th</td></tr>
<tr><td>2</td><td>Jack</td><td>5th</td></tr>
<tr><td>3</td><td>Michel</td><td>6th</td></tr>
<tr><td>4</td><td>Mike</td><td>7th</td></tr>
<tr><td>5</td><td>Yke</td><td>8th</td></tr>
<tr><td>6</td><td>4ke</td><td>9th</td></tr>
<tr><td>7</td><td>7ke</td><td>10th</td></tr>
</tbody>
</table>
$('tr').on('click',function(e)
{
var objTR=$(this);
});
I have to select multiple rows using control key.
And then store Student ID in array.
How should i do using jquery Click event.

If you only want the cells to light up when the control key is pressed, this code does the trick:
var studendIds = [];
$(window).on('keydown',(function()
{
var target = $('tr'),
root = $(window),
clickCb = function(e)
{
if (!$(this).hasClass('ui-selected'))
{
$(this).addClass('ui-selected');
//add id to array
studentIds.push(+(this.cells[0].innerHTML))
}
else
{
$(this).removeClass('ui-selected');
for(var i=0;i<studentIds.length;i++)
{
if (studentIds[i] === +(this.cells[0].innerHTML))
{//remove id from array
delete studentIds[i];
break;
}
}
}
},
upCb = function(e)
{
target.off('click',clickCb);
root.on('keydown',downCb);
root.off('keyup',upCb);
},
downCb = function(e)
{
if (e.which === 17 || e.which === 16)
{//17 is ctrl, 16 is shift
root.off('keydown',downCb);
root.on('keyup',upCb);
target.on('click',clickCb);
}
};
return downCb;
}()));
Fiddle demo.
What this code does, essentially, is listen for a keydown event. If that key is the ctrl key (code 17), a click listener is attached, that will set/unset the ui-selected class if a particular row is clicked. The handler also detaches the keydown listener itself and attaches a keyup listener that sets up the event listeners back to their original states once the ctrl key is released.
Meanwhile, another listener is attached, that picks up on the keyup event. If the key (ctrl) is released, the click listener is removed, and the keydown event listener is restored.
As I said in the comments, though the code above does keep track of which ids are selected, I'd personally not do that.
Whenever you need those ids (probably on form submission, or to perform an ajax request), seeing as you have those rows marked usign a class, I'd just do this:
function assumingAjaxFunction()
{
var data = {some: 'boring', stuff: 'you might send', ids: []};
$('.ui-selected > td:first').each(function()
{
data.ids.push($(this).text());
});
console.log(data.ids);//array of ids
}
VanillaJS fiddle with shift-select support
and the code to go with it:
window.addEventListener('load',function load()
{
'use strict';
var tbl = document.getElementById('tableStudent');
window.addEventListener('keydown',(function()
{
var expr = /\bui\-selected\b/i,
key, prev,
clickCb = function(e)
{
e = e || window.event;
var i, target = (function(elem)
{//get the row element, in case user clicked on cell
if (elem.tagName.toLowerCase() === 'th')
{//head shouldn't be clickable
return elem;
}
while(elem !== tbl)
{//if elem is tbl, we can't determine which row was clicked anyway
if (elem.tagName.toLowerCase() === 'tr')
{//row found, break
break;
}
elem = elem.parentNode;//if td clicked, goto parent (ie tr)
}
return elem;
}(e.target || e.srcElement));
if (target.tagName.toLowerCase() !== 'tr')
{//either head, table or something else was clicked
return e;//stop handler
}
if (expr.test(target.className))
{//if row WAS selected, unselect it
target.className = target.className.replace(expr, '');
}
else
{//target was not selected
target.className += ' ui-selected';//set class
}
if (key === 17)
{//ctrl-key was pressed, so end handler here
return e;
}
//key === 16 here, handle shift event
if (prev === undefined)
{//first click, set previous and return
prev = target;
return e;
}
for(i=1;i<tbl.rows.length;i++)
{//start at 1, because head is ignored
if (tbl.rows[i] === target)
{//select from bottom to top
break;
}
if (tbl.rows[i] === prev)
{//top to bottom
prev = target;//prev is bottom row to select
break;
}
}
for(i;i<tbl.rows.length;i++)
{
if (!expr.test(tbl.rows[i].className))
{//if cel is not selected yet, select it
tbl.rows[i].className += 'ui-selected';
}
if (tbl.rows[i] === prev)
{//we've reached the previous cell, we're done
break;
}
}
},
upCb = function(e)
{
prev = undefined;//clear prev reference, if set
window.addEventListener('keydown',downCb,false);//restore keydown listener
tbl.removeEventListener('click',clickCb, false);//remove click
window.removeEventListener('keyup',upCb,false);//and keyup listeners
},
downCb = function(e)
{//this is the actual event handler
e= e || window.event;
key = e.which || e.keyCode;//which key was pressed
if (key === 16 || key === 17)
{//ctrl or shift:
window.removeEventListener('keydown',downCb,false);//ignore other keydown events
tbl.addEventListener('click',clickCb,false);//listen for clicks
window.addEventListener('keyup', upCb, false);//register when key is released
}
};
return downCb;//return handled
}()), false);
window.removeEventListener('load',load,false);
}, false);
This code is close to copy-paste ready, so please, at least give it a chance. Check the fiddle, it works fine for me. It passes JSlint in with fairly strict settings, too (/*jslint browser: true, white: true */), so it's safe to say this code isn't that bad. Yes it may look somewhat complicated. But a quick read-up about how event delegation works will soon turn out that delegating an event is easier than you think
This code also heavily uses closures, a powerful concept which, in essence isn't really that hard to understand either, this linked answer uses images that came from this article: JavaScript closures explained. It's a fairly easy read, but it does a great job. After you've read this, you'll see closures as essential, easy, powerful and undervalued constructs, promise

First of all, define some classes which will indicate that you have selected a table row:
tr.selected, tr.selected td {
background: #ffc; /* light-red variant */
}
Then write this jQuery event handler:
$('table#tableStudent').on('click', 'tr', function() {
if($(this).hasClass('selected')) {
// this accours during the second click - unselecting the row
$(this).removeClass('selected');
} else {
// here you are selecting a row, adding a new class "selected" into <tr> element.
// you can reverse the IF condition to get this one up above, and the unselecting case down.
$(this).addClass('selected');
}
});
In this way you have the expirence that you have selected a row. If you have a column which contains a checkbox, or something similar, you might want to do that logic inside the event listener I provided you above.

This might help DEMO:
function bindMultipleSelect(element){
var self = this;
var isCtrlDown = false;
element.on('click', 'tr', function(){
var tr = $(this);
if(!isCtrlDown)
return;
tr.toggleClass('ui-selected')
})
$(document).on('keydown', function(e){
isCtrlDown = (e.which === 17)
});
$(document).on('keyup', function(e){
isCtrlDown = !(e.which === 17)
});
self.getSelectedRows = function(){
var arr = [];
element.find('.ui-selected').each(function(){
arr.push($(this).find('td').eq(0).text())
})
return arr;
}
return self;
}
window.myElement = bindMultipleSelect($('#tableStudent'))

Related

How to trigger the select function when the value is selected after the tab is pressed

I have a combo box that enables the autocomplete function.
The project that I am working on currently uses the same code that is mentioned in the answer.
I have modified the code to select the value on the tab pressed if it matched the exact text in the select.
In the source, I called this function.
this._addTabAndReturnListener(matchedOptions);
and I have implemented _addTabAndReturnListener as:
_addTabAndReturnListener: function(options) {
var self = this;
var element = this.element;
var input = this.input[0];
this.input.off('keydown.first'); // Disable listener from previous call
this.input.on('keydown.first', function(e) {
var keycode = e.which;
if (keycode == 9 || keycode == 13) // If tab key pressed
if (options.length == 1) {
var result = null;
element.children("option").each(function() {
if ($(this).text() == options[0].value)
result = this;
})
if (result != null)
input.value = $(result).text();
$(this).attr("title", input.value + " matched").tooltip("open");
input.dispatchEvent(new Event("select"));
}
});
},
I wanted to trigger the select function when the value is selected after the tab is pressed, but not able to do so.
Normally when a value is selected from the dropdown the following functions have been triggered, and the alert is displayed. But it is not working on the tab pressed.
$('#myselect').combobox({
select: function(event, ui) {
alert(event.target);
}
});
What could be the solution for this?
Jsfiddle
I was able to solve this issue by adding a trigger.
$(this).trigger("tabOnChange", result);
and define a new event, called tabOnChange
tabOnChange: function(event, opt) {
opt.selected = true;
this._trigger("select", event, {
item: opt
});
},
Fiddle:

Bind hover (mouseenter / mouseleave) event to dynamic content with Javascript (without Jquery)

I wrote an event delegation function in javascript:
function matches(el, selector) {
var test = (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector);
if (test)
return test.call(el, selector);
return false;
}
function delegation(node, child, evt, fn, limit) {
node.addEventListener(evt, function (e) {
//maximum number of ancestors i'm going to check
limit = limit ? limit : 2;
e = e || event;
var target = e.target || e.srcElement, i = 0, fire = false;
while (target) {
if (matches(target, child)) {
//break out of the loop if i find the matching DOM element, then fire the event
fire = true;
break;
}
if (i > limit) {
break;
}
i++;
//If event.target doesn't has id/class/tag "child", check its ancestors.
target = target.parentNode;
}
if (fire) {
fn(target, e);
}
}, false);
}
Usage: delegation(document, 'class-or-id-or-tagName', 'event-name', function, query-limit);
It works relatively well until I stumbled upon mouse enter and mouse leave events. The problem is that the events are only triggered when my mouse leave or enter document window, not DOM element, I do understand the problem why but I can't seem to fix it. Is there any way to replicate
$(document).on('mouseenter, DOM , function).on('mouseleave', DOM, function);
in pure Javascript.
Edit: Thanks for all the comments, I found out that there's nothing wrong with my code. I just need to use the correct event name when calling the delegation function, mouseenter should be mouseover, mouseleave should be mouseout.
Changing from
delegation(document, '.some-class-name', 'mouseenter', function(){});
to
delegation(document, '.some-class-name', 'mouseover', function(){});
works wonder.

Javascript - free a memorized function

I must say that I do not know the right terminology, so please forgive my some mistakes.
What I have is a Table with rows. The last editable column of the last row in the table is enabled with a TabKey which automatically creates a new row.
function enableTab() {
lastEditable = currentTable
.find("tr")
.find('textarea, .cleanbox-editable-cell')
.not(function(index, element) {
return $(element).parent().css("display") == "none";
})
.last();
lastEditable.unbind("keydown");
lastEditable.keydown(function(e) {
var keyCode = e.keyCode || e.which;
createNewRow();
};
};
function createNewRow() {
createRow();
enableTab();
}
When the new row is created, the lastEditable variable is re-calculated.
What I want is lastEditable to be associated only to the last column of the last row. Anyway, what happens is that a new row is created, and both the new and previous rows' last columns are furnished with the function to create new rows, when I want only the new row to be be able to perform such a function.
So, in my opinion, what happens is that: everytime a new row is created, the variable lastEditable is rightly associated to the last column of the new row. But the function of the object previously associated to that same variable is not deleted: it simply doesn't have a name anymore.
Am I kind of right? How can I delete an object?
I tried the following, but it didn't work:
lastEditable.keydown(function(e) {
var keyCode = e.keyCode || e.which;
delete lastEditable;
createNewRow();
};
You are unbinding the event handler, then immediately reapplying the same event handler to the same element. You need to re-organise your code so that you bind the event handler to the new element as you create the new row http://jsfiddle.net/2fLrsjcz/
var currentTable = $('table');
function bindKeyDown(lastEditable) {
lastEditable.keydown(function(e) {
if(e.keyCode == 9) {
createNewRow($(this));
}
});
}
function getLastEditable() {
return currentTable
.find("tr")
.find('textarea, .cleanbox-editable-cell')
.not(function(index, element) {
return $(element).parent().css("display") == "none";
})
.last();
}
function createNewRow($lastEditable) {
var newEditable = $lastEditable.parent().parent().clone().appendTo(currentTable);
$lastEditable.unbind("keydown");
bindKeyDown(getLastEditable());
}
bindKeyDown(getLastEditable());
To improve this post that may be useful to others, studying Lee's code I have solved my problem just moving lastEditable.unbind("keydown") into .keydown(function(e) {}), as following:
function enableTab() {
lastEditable = currentTable
.find("tr")
.find('textarea, .cleanbox-editable-cell')
.not(function(index, element) {
return $(element).parent().css("display") == "none";
})
.last();
lastEditable.keydown(function(e) {
var keyCode = e.keyCode || e.which;
lastEditable.unbind("keydown");
createNewRow();
};
};
function createNewRow() {
createRow();
enableTab();
}

How can I prevent the backspace key from navigating back?

On IE I can do this with the (terribly non-standard, but working) jQuery
if ($.browser.msie)
$(document).keydown(function(e) { if (e.keyCode == 8) window.event.keyCode = 0;});
But is it possible to do in a way which works on Firefox, or in a cross-browser way for a bonus?
For the record:
$(document).keydown(function(e) { if (e.keyCode == 8) e.stopPropagation(); });
does nothing.
$(document).keydown(function(e) { if (e.keyCode == 8) e.preventDefault(); });
solves the problem, but renders the backspace key unusable on the page, which is even worse than the original behaviour.
EDIT:
The reason I do this is that I'm not creating a simple web page but a large application. It is incredibly annoying to lose 10 minutes of work just because you pressed backspace in the wrong place. The ratio of preventing mistakes vs. annoying users should be way above 1000/1 by preventing the backspace key from navigating back.
EDIT2: I'm not trying to prevent history navigation, just accidents.
EDIT3: #brentonstrines comment (moved here since the question is so popular): This is a long-term 'fix', but you could throw your support behind the Chromium bug to change this behavior in webkit
This code solves the problem, at least in IE and Firefox (haven't tested any other, but I give it a reasonable chance of working if the problem even exists in other browsers).
// Prevent the backspace key from navigating back.
$(document).unbind('keydown').bind('keydown', function (event) {
if (event.keyCode === 8) {
var doPrevent = true;
var types = ["text", "password", "file", "search", "email", "number", "date", "color", "datetime", "datetime-local", "month", "range", "search", "tel", "time", "url", "week"];
var d = $(event.srcElement || event.target);
var disabled = d.prop("readonly") || d.prop("disabled");
if (!disabled) {
if (d[0].isContentEditable) {
doPrevent = false;
} else if (d.is("input")) {
var type = d.attr("type");
if (type) {
type = type.toLowerCase();
}
if (types.indexOf(type) > -1) {
doPrevent = false;
}
} else if (d.is("textarea")) {
doPrevent = false;
}
}
if (doPrevent) {
event.preventDefault();
return false;
}
}
});
This code works on all browsers and swallows the backspace key when not on a form element, or if the form element is disabled|readOnly. It is also efficient, which is important when it is executing on every key typed in.
$(function(){
/*
* this swallows backspace keys on any non-input element.
* stops backspace -> back
*/
var rx = /INPUT|SELECT|TEXTAREA/i;
$(document).bind("keydown keypress", function(e){
if( e.which == 8 ){ // 8 == backspace
if(!rx.test(e.target.tagName) || e.target.disabled || e.target.readOnly ){
e.preventDefault();
}
}
});
});
The other answers here have established that this cannot be done without whitelisting elements in which Backspace is allowed. This solution is not ideal because the whitelist is not as straightforward as merely textareas and text/password inputs, but is repeatedly found to be incomplete and needing to be updated.
However, since the purpose of suppressing the backspace functionality is merely to prevent users from accidentally losing data, the beforeunload solution is a good one because the modal popup is surprising--modal popups are bad when they are triggered as part of a standard workflow, because the user gets used to dismissing them without reading them, and they are annoying. In this case, the modal popup would only appear as an alternative to a rare and surprising action, and is therefore acceptable.
The problem is that an onbeforeunload modal must not pop up whenever the user navigates away from the page (such as when clicking a link or submitting a form), and we don't want to start whitelisting or blacklisting specific onbeforeunload conditions.
The ideal combination of tradeoffs for a generalized solution is as follows: keep track of whether backspace is pressed, and only pop up the onbeforeunload modal if it is. In other words:
function confirmBackspaceNavigations () {
// http://stackoverflow.com/a/22949859/2407309
var backspaceIsPressed = false
$(document).keydown(function(event){
if (event.which == 8) {
backspaceIsPressed = true
}
})
$(document).keyup(function(event){
if (event.which == 8) {
backspaceIsPressed = false
}
})
$(window).on('beforeunload', function(){
if (backspaceIsPressed) {
backspaceIsPressed = false
return "Are you sure you want to leave this page?"
}
})
} // confirmBackspaceNavigations
This has been tested to work in IE7+, FireFox, Chrome, Safari, and Opera. Just drop this function into your global.js and call it from any page where you don't want users to accidentally lose their data.
Note that an onbeforeunload modal can only be triggered once, so if the user presses backspace again, the modal will not fire again.
Note that this will not trigger on hashchange events, however in that context you can use other techniques to keep users from accidentally losing their data.
A more elegant/concise solution:
$(document).on('keydown',function(e){
var $target = $(e.target||e.srcElement);
if(e.keyCode == 8 && !$target.is('input,[contenteditable="true"],textarea'))
{
e.preventDefault();
}
})
Modification of erikkallen's Answer to address different input types
I've found that an enterprising user might press backspace on a checkbox or a radio button in a vain attempt to clear it and instead they would navigate backwards and lose all of their data.
This change should address that issue.
New Edit to address content editable divs
//Prevents backspace except in the case of textareas and text inputs to prevent user navigation.
$(document).keydown(function (e) {
var preventKeyPress;
if (e.keyCode == 8) {
var d = e.srcElement || e.target;
switch (d.tagName.toUpperCase()) {
case 'TEXTAREA':
preventKeyPress = d.readOnly || d.disabled;
break;
case 'INPUT':
preventKeyPress = d.readOnly || d.disabled ||
(d.attributes["type"] && $.inArray(d.attributes["type"].value.toLowerCase(), ["radio", "checkbox", "submit", "button"]) >= 0);
break;
case 'DIV':
preventKeyPress = d.readOnly || d.disabled || !(d.attributes["contentEditable"] && d.attributes["contentEditable"].value == "true");
break;
default:
preventKeyPress = true;
break;
}
}
else
preventKeyPress = false;
if (preventKeyPress)
e.preventDefault();
});
Example
To test make 2 files.
starthere.htm - open this first so you have a place to go back to
Navigate to here to test
test.htm - This will navigate backwards when backspace is pressed while the checkbox or submit has focus (achieved by tabbing). Replace with my code to fix.
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).keydown(function(e) {
var doPrevent;
if (e.keyCode == 8) {
var d = e.srcElement || e.target;
if (d.tagName.toUpperCase() == 'INPUT' || d.tagName.toUpperCase() == 'TEXTAREA') {
doPrevent = d.readOnly || d.disabled;
}
else
doPrevent = true;
}
else
doPrevent = false;
if (doPrevent)
e.preventDefault();
});
</script>
</head>
<body>
<input type="text" />
<input type="radio" />
<input type="checkbox" />
<input type="submit" />
</body>
</html>
Based on the comments it appears you want to stop people losing information in forms, if they press backspace to delete but the field is not focused.
In which case, you want to look at the onunload event handler. Stack Overflow uses it - if you try to leave a page when you've started writing an answer, it pops up a warning.
Most of the answers are in jquery. You can do this perfectly in pure Javascript, simple and no library required. This article was a good starting point for me but since keyIdentifier is deprecated, I wanted this code to be more secure so I added ||e.keyCode==8 to the if statement. Also, the code wasn't working well on Firefox so I added return false; and now it works perfectly well. Here it is:
<script type="text/javascript">
window.addEventListener('keydown',function(e){if(e.keyIdentifier=='U+0008'||e.keyIdentifier=='Backspace'||e.keyCode==8){if(e.target==document.body){e.preventDefault();return false;}}},true);
</script>
This code works great because,
It is in pure javascript (no library required).
Not only it checks the key pressed, it confirms if the action is really a browser "back" action.
Together with the above, user can type and delete text from input text boxes on the web page without any problems while still preventing the back button action.
It is short, clean, fast and straight to the point.
You can add console.log(e); for your your test purposes, and hit F12 in chrome, go to "console" tab and hit "backspace" on the page and look inside it to see what values are returned, then you can target all of those parameters to further enhance the code above to suit your needs.
Stop from navigating this code works!
$(document).on("keydown", function (event) {
if (event.keyCode === 8) {
event.preventDefault();
}
});
But for not to restricting text fields but others
$(document).on("keydown", function (event) {
if (event.which === 8 && !$(event.target).is("input, textarea")) {
event.preventDefault();
}
});
To prevent it for specific field simply use
$('#myOtherField').on("keydown", function (event) {
if (event.keyCode === 8 || event.which === 8) {
event.preventDefault();
}
});
Referring to this one below!
Prevent BACKSPACE from navigating back with jQuery (Like Google's Homepage)
Combining solutions given by "thetoolman" && "Biff MaGriff"
following code seems to work correctly in IE 8/Mozilla/Chrome
$(function () {
var rx = /INPUT|TEXTAREA/i;
var rxT = /RADIO|CHECKBOX|SUBMIT/i;
$(document).bind("keydown keypress", function (e) {
var preventKeyPress;
if (e.keyCode == 8) {
var d = e.srcElement || e.target;
if (rx.test(e.target.tagName)) {
var preventPressBasedOnType = false;
if (d.attributes["type"]) {
preventPressBasedOnType = rxT.test(d.attributes["type"].value);
}
preventKeyPress = d.readOnly || d.disabled || preventPressBasedOnType;
} else {preventKeyPress = true;}
} else { preventKeyPress = false; }
if (preventKeyPress) e.preventDefault();
});
});
Not sure why no-one's just answered this - seems like a perfectly reasonable technical question to ask whether it's possible.
No, I don't think there's a cross-browser way to disable the backspace button. I know it's not enabled by default in FF these days though.
I had a hard time finding a non-JQUERY answer. Thanks to Stas for putting me on the track.
Chrome: If you don't need cross browser support, you can just use a blacklist, rather than whitelisting. This pure JS version works in Chrome, but not in IE. Not sure about FF.
In Chrome (ver. 36, mid 2014), keypresses not on an input or contenteditable element seem to be targeted to <BODY>. This makes it possible use a blacklist, which I prefer to whitelisting. IE uses the last click target - so it might be a div or anything else. That makes this useless in IE.
window.onkeydown = function(event) {
if (event.keyCode == 8) {
//alert(event.target.tagName); //if you want to see how chrome handles keypresses not on an editable element
if (event.target.tagName == 'BODY') {
//alert("Prevented Navigation");
event.preventDefault();
}
}
}
Cross Browser: For pure javascript, I found Stas' answer to be the best. Adding one more condition check for contenteditable made it work for me*:
document.onkeydown = function(e) {stopDefaultBackspaceBehaviour(e);}
document.onkeypress = function(e) {stopDefaultBackspaceBehaviour(e);}
function stopDefaultBackspaceBehaviour(event) {
var event = event || window.event;
if (event.keyCode == 8) {
var elements = "HTML, BODY, TABLE, TBODY, TR, TD, DIV";
var d = event.srcElement || event.target;
var regex = new RegExp(d.tagName.toUpperCase());
if (d.contentEditable != 'true') { //it's not REALLY true, checking the boolean value (!== true) always passes, so we can use != 'true' rather than !== true/
if (regex.test(elements)) {
event.preventDefault ? event.preventDefault() : event.returnValue = false;
}
}
}
}
*Note that IEs [edit: and Spartan/TechPreview] have a "feature" that makes table-related elements uneditable. If you click one of those and THEN press backspace, it WILL navigate back. If you don't have editable <td>s, this is not an issue.
This solution is similar to others that have been posted, but it uses a simple whitelist making it easily customizable to allow the backspace in specified elements just by setting the selector in the .is() function.
I use it in this form to prevent the backspace on pages from navigating back:
$(document).on("keydown", function (e) {
if (e.which === 8 && !$(e.target).is("input:not([readonly]), textarea")) {
e.preventDefault();
}
});
To elaborate slightly on #erikkallen's excellent answer, here is a function that allows all keyboard-based input types, including those introduced in HTML5:
$(document).unbind('keydown').bind('keydown', function (event) {
var doPrevent = false;
var INPUTTYPES = [
"text", "password", "file", "date", "datetime", "datetime-local",
"month", "week", "time", "email", "number", "range", "search", "tel",
"url"];
var TEXTRE = new RegExp("^" + INPUTTYPES.join("|") + "$", "i");
if (event.keyCode === 8) {
var d = event.srcElement || event.target;
if ((d.tagName.toUpperCase() === 'INPUT' && d.type.match(TEXTRE)) ||
d.tagName.toUpperCase() === 'TEXTAREA') {
doPrevent = d.readOnly || d.disabled;
} else {
doPrevent = true;
}
}
if (doPrevent) {
event.preventDefault();
}
});
JavaScript - jQuery way:
$(document).on("keydown", function (e) {
if (e.which === 8 && !$(e.target).is("input, textarea")) {
e.preventDefault();
}
});
Javascript - the native way, that works for me:
<script type="text/javascript">
//on backspace down + optional callback
function onBackspace(e, callback){
var key;
if(typeof e.keyIdentifier !== "undefined"){
key = e.keyIdentifier;
}else if(typeof e.keyCode !== "undefined"){
key = e.keyCode;
}
if (key === 'U+0008' ||
key === 'Backspace' ||
key === 8) {
if(typeof callback === "function"){
callback();
}
return true;
}
return false;
}
//event listener
window.addEventListener('keydown', function (e) {
switch(e.target.tagName.toLowerCase()){
case "input":
case "textarea":
break;
case "body":
onBackspace(e,function(){
e.preventDefault();
});
break;
}
}, true);
</script>
I had some problems with the accepted solution and the Select2.js plugin; I was not able to delete characters in the editable box as the delete action was being prevented. This was my solution:
//Prevent backwards navigation when trying to delete disabled text.
$(document).unbind('keydown').bind('keydown', function (event) {
if (event.keyCode === 8) {
var doPrevent = false,
d = event.srcElement || event.target,
tagName = d.tagName.toUpperCase(),
type = (d.type ? d.type.toUpperCase() : ""),
isEditable = d.contentEditable,
isReadOnly = d.readOnly,
isDisabled = d.disabled;
if (( tagName === 'INPUT' && (type === 'TEXT' || type === 'PASSWORD'))
|| tagName === 'PASSWORD'
|| tagName === 'TEXTAREA') {
doPrevent = isReadOnly || isDisabled;
}
else if(tagName === 'SPAN'){
doPrevent = !isEditable;
}
else {
doPrevent = true;
}
}
if (doPrevent) {
event.preventDefault();
}
});
Select2 creates a Span with an attribute of "contentEditable" which is set to true for the editable combo box in it. I added code to account for the spans tagName and the different attribute. This solved all my problems.
Edit: If you are not using the Select2 combobox plugin for jquery, then this solution may not be needed by you, and the accepted solution might be better.
document.onkeydown = function (e) {
e.stopPropagation();
if ((e.keyCode==8 || e.keyCode==13) &&
(e.target.tagName != "TEXTAREA") &&
(e.target.tagName != "INPUT")) {
return false;
}
};
This code solves the problem in all browsers:
onKeydown:function(e)
{
if (e.keyCode == 8)
{
var d = e.srcElement || e.target;
if (!((d.tagName.toUpperCase() == 'BODY') || (d.tagName.toUpperCase() == 'HTML')))
{
doPrevent = false;
}
else
{
doPrevent = true;
}
}
else
{
doPrevent = false;
}
if (doPrevent)
{
e.preventDefault();
}
}
Simplest way to prevent navigation on pressing backspace
$(document).keydown(function () {
if (event.keyCode == 8) {
if (event.target.nodeName == 'BODY') {
event.preventDefault();
}
}
});
Using Dojo toolkit 1.7, this works in IE 8:
require(["dojo/on", "dojo/keys", "dojo/domReady!"],
function(on, keys) {
on(document.body,"keydown",function(evt){if(evt.keyCode == keys.BACKSPACE)evt.preventDefault()});
});
Have you tried the very simple solution of just adding the following attribute to your read only text field:
onkeydown="return false;"
This will keep the browser from going back in history when the Backspace key is pressed in a read only text field. Maybe I am missing your true intent, but seems like this would be the simplest solution to your issue.
A much neater solution -
$(document).on('keydown', function (e) {
var key = e == null ? event.keyCode : e.keyCode;
if(key == 8 && $(document.activeElement.is(':not(:input)'))) //select, textarea
e.preventDefault();
});
Alternately, you could only check if
$(document.activeElement).is('body')
Pure javascript version, which works in all browsers:
document.onkeydown = function(e) {stopDefaultBackspaceBehaviour(e);}
document.onkeypress = function(e) {stopDefaultBackspaceBehaviour(e);}
function stopDefaultBackspaceBehaviour(event) {
var event = event || window.event;
if (event.keyCode == 8) {
var elements = "HTML, BODY, TABLE, TBODY, TR, TD, DIV";
var d = event.srcElement || event.target;
var regex = new RegExp(d.tagName.toUpperCase());
if (regex.test(elements)) {
event.preventDefault ? event.preventDefault() : event.returnValue = false;
}
}
}
Of course you can use "INPUT, TEXTAREA" and use "if (!regex.test(elements))" then. The first worked fine for me.
Performance?
I was worried about performance and made a fiddle: http://jsfiddle.net/felvhage/k2rT6/9/embedded/result/
var stresstest = function(e, method, index){...
I have analyzed the most promising methods i found in this thread. It turns out, they were all very fast and most probably do not cause a problem in terms of "sluggishness" when typing.
The slowest Method i looked at was about 125 ms for 10.000 Calls in IE8. Which is 0.0125ms per Stroke.
I found the methods posted by Codenepal and Robin Maben to be fastest ~ 0.001ms (IE8) but beware of the different semantics.
Perhaps this is a relief to someone introducing this kind of functionality to his code.
Modified erikkallen answer:
$(document).unbind('keydown').bind('keydown', function (event) {
var doPrevent = false, elem;
if (event.keyCode === 8) {
elem = event.srcElement || event.target;
if( $(elem).is(':input') ) {
doPrevent = elem.readOnly || elem.disabled;
} else {
doPrevent = true;
}
}
if (doPrevent) {
event.preventDefault();
return false;
}
});
This solution worked very well when tested.
I did add some code to handle some input fields not tagged with input, and to integrate in an Oracle PL/SQL application that generates an input form for my job.
My "two cents":
if (typeof window.event != ''undefined'')
document.onkeydown = function() {
//////////// IE //////////////
var src = event.srcElement;
var tag = src.tagName.toUpperCase();
if (event.srcElement.tagName.toUpperCase() != "INPUT"
&& event.srcElement.tagName.toUpperCase() != "TEXTAREA"
|| src.readOnly || src.disabled
)
return (event.keyCode != 8);
if(src.type) {
var type = ("" + src.type).toUpperCase();
return type != "CHECKBOX" && type != "RADIO" && type != "BUTTON";
}
}
else
document.onkeypress = function(e) {
//////////// FireFox
var src = e.target;
var tag = src.tagName.toUpperCase();
if ( src.nodeName.toUpperCase() != "INPUT" && tag != "TEXTAREA"
|| src.readOnly || src.disabled )
return (e.keyCode != 8);
if(src.type) {
var type = ("" + src.type).toUpperCase();
return type != "CHECKBOX" && type != "RADIO" && type != "BUTTON";
}
}
I created a NPM project with a clean version of the currently accepted (of erikkallen)
https://github.com/slorber/backspace-disabler
It uses basically the same principles but:
No dependency
Support for contenteditable
More readable / maintainable code base
Will be supported as it will be used in production by my company
MIT license
var Backspace = 8;
// See http://stackoverflow.com/questions/12949590/how-to-detach-event-in-ie-6-7-8-9-using-javascript
function addHandler(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
}
function removeHandler(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
}
// Test wether or not the given node is an active contenteditable,
// or is inside an active contenteditable
function isInActiveContentEditable(node) {
while (node) {
if ( node.getAttribute && node.getAttribute("contenteditable") === "true" ) {
return true;
}
node = node.parentNode;
}
return false;
}
var ValidInputTypes = ['TEXT','PASSWORD','FILE','EMAIL','SEARCH','DATE'];
function isActiveFormItem(node) {
var tagName = node.tagName.toUpperCase();
var isInput = ( tagName === "INPUT" && ValidInputTypes.indexOf(node.type.toUpperCase()) >= 0 );
var isTextarea = ( tagName === "TEXTAREA" );
if ( isInput || isTextarea ) {
var isDisabled = node.readOnly || node.disabled;
return !isDisabled;
}
else if ( isInActiveContentEditable(node) ) {
return true;
}
else {
return false;
}
}
// See http://stackoverflow.com/questions/1495219/how-can-i-prevent-the-backspace-key-from-navigating-back
function disabler(event) {
if (event.keyCode === Backspace) {
var node = event.srcElement || event.target;
// We don't want to disable the ability to delete content in form inputs and contenteditables
if ( isActiveFormItem(node) ) {
// Do nothing
}
// But in any other cases we prevent the default behavior that triggers a browser backward navigation
else {
event.preventDefault();
}
}
}
/**
* By default the browser issues a back nav when the focus is not on a form input / textarea
* But users often press back without focus, and they loose all their form data :(
*
* Use this if you want the backspace to never trigger a browser back
*/
exports.disable = function(el) {
addHandler(el || document,"keydown",disabler);
};
/**
* Reenable the browser backs
*/
exports.enable = function(el) {
removeHandler(el || document,"keydown",disabler);
};
Here is my rewrite of the top-voted answer. I tried to check element.value!==undefined (since some elements like may have no html attribute but may have a javascript value property somewhere on the prototype chain), however that didn't work very well and had lots of edge cases. There doesn't seem to be a good way to future-proof this, so a whitelist seems the best option.
This registers the element at the end of the event bubble phase, so if you want to handle Backspace in any custom way, you can do so in other handlers.
This also checks instanceof HTMLTextAreElement since one could theoretically have a web component which inherits from that.
This does not check contentEditable (combine with other answers).
https://jsfiddle.net/af2cfjc5/15/
var _INPUTTYPE_WHITELIST = ['text', 'password', 'search', 'email', 'number', 'date'];
function backspaceWouldBeOkay(elem) {
// returns true if backspace is captured by the element
var isFrozen = elem.readOnly || elem.disabled;
if (isFrozen) // a frozen field has no default which would shadow the shitty one
return false;
else {
var tagName = elem.tagName.toLowerCase();
if (elem instanceof HTMLTextAreaElement) // allow textareas
return true;
if (tagName=='input') { // allow only whitelisted input types
var inputType = elem.type.toLowerCase();
if (_INPUTTYPE_WHITELIST.includes(inputType))
return true;
}
return false; // everything else is bad
}
}
document.body.addEventListener('keydown', ev => {
if (ev.keyCode==8 && !backspaceWouldBeOkay(ev.target)) {
//console.log('preventing backspace navigation');
ev.preventDefault();
}
}, true); // end of event bubble phase
Sitepoint: Disable back for Javascript
event.stopPropagation() and event.preventDefault() do nothing in IE. I had to send return event.keyCode == 11 (I just picked something) instead of just saying "if not = 8, run the event" to make it work, though. event.returnValue = false also works.
Another method using jquery
<script type="text/javascript">
//set this variable according to the need within the page
var BACKSPACE_NAV_DISABLED = true;
function fnPreventBackspace(event){if (BACKSPACE_NAV_DISABLED && event.keyCode == 8) {return false;}}
function fnPreventBackspacePropagation(event){if(BACKSPACE_NAV_DISABLED && event.keyCode == 8){event.stopPropagation();}return true;}
$(document).ready(function(){
if(BACKSPACE_NAV_DISABLED){
//for IE use keydown, for Mozilla keypress
//as described in scr: http://www.codeproject.com/KB/scripting/PreventDropdownBackSpace.aspx
$(document).keypress(fnPreventBackspace);
$(document).keydown(fnPreventBackspace);
//Allow Backspace is the following controls
var jCtrl = null;
jCtrl = $('input[type="text"]');
jCtrl.keypress(fnPreventBackspacePropagation);
jCtrl.keydown(fnPreventBackspacePropagation);
jCtrl = $('input[type="password"]');
jCtrl.keypress(fnPreventBackspacePropagation);
jCtrl.keydown(fnPreventBackspacePropagation);
jCtrl = $('textarea');
jCtrl.keypress(fnPreventBackspacePropagation);
jCtrl.keydown(fnPreventBackspacePropagation);
//disable backspace for readonly and disabled
jCtrl = $('input[type="text"][readonly="readonly"]')
jCtrl.keypress(fnPreventBackspace);
jCtrl.keydown(fnPreventBackspace);
jCtrl = $('input[type="text"][disabled="disabled"]')
jCtrl.keypress(fnPreventBackspace);
jCtrl.keydown(fnPreventBackspace);
}
});
</script>
I've been using this in my code for some time now. I write online tests for students and ran into the problem when students were pressing backspace during their test and it would take them back to the login screen. Frustrating! It works on FF for sure.
document.onkeypress = Backspace;
function Backspace(event) {
if (event.keyCode == 8) {
if (document.activeElement.tagName == "INPUT") {
return true;
} else {
return false;
}
}
}

Detecting data changes in forms using jQuery

I'm using ASP.NET 2.0 with a Master Page, and I was wondering if anyone knew of a way to detect when the fields within a certain <div> or fieldset have been changed (e.g., marked 'IsDirty')?
You could bind the Change event for all inputs and flag a variable as true. Like this.
var somethingChanged = false;
$(document).ready(function() {
$('input').change(function() {
somethingChanged = true;
});
});
But, keep in mind that if the user changes something, then changes back to the original values, it will still be flagged as changed.
UPDATE: For a specific div or fieldset. Just use the id for the given fieldset or div. Example:
var somethingChanged = false;
$(document).ready(function() {
$('#myDiv input').change(function() {
somethingChanged = true;
});
});
Quick (but very dirty) solution
This is quick, but it won't take care of ctrl+z or cmd+z and it will give you a false positive when pressing shift, ctrl or the tab key:
$('#my-form').on('change keyup paste', ':input', function(e) {
// The form has been changed. Your code here.
});
Test it with this fiddle.
Quick (less dirty) solution
This will prevent false positives for shift, ctrl or the tab key, but it won't handle ctrl+z or cmd+z:
$('#my-form').on('change keyup paste', ':input', function(e) {
var keycode = e.which;
if (e.type === 'paste' || e.type === 'change' || (
(keycode === 46 || keycode === 8) || // delete & backspace
(keycode > 47 && keycode < 58) || // number keys
keycode == 32 || keycode == 13 || // spacebar & return key(s) (if you want to allow carriage returns)
(keycode > 64 && keycode < 91) || // letter keys
(keycode > 95 && keycode < 112) || // numpad keys
(keycode > 185 && keycode < 193) || // ;=,-./` (in order)
(keycode > 218 && keycode < 223))) { // [\]' (in order))
// The form has been changed. Your code here.
}
});
Test it with this fiddle.
A complete solution
If you want to handle all the cases, you should use:
// init the form when the document is ready or when the form is populated after an ajax call
$(document).ready(function() {
$('#my-form').find(':input').each(function(index, value) {
$(this).data('val', $(this).val());
});
})
$('#my-form').on('change paste', ':input', function(e) {
$(this).data('val', $(this).val());
// The form has been changed. Your code here.
});
$('#my-form').on('keyup', ':input', function(e) {
if ($(this).val() != $(this).data('val')) {
$(this).data('val', $(this).val());
// The form has been changed. Your code here.
}
});
Test it with this fiddle.
A simple and elegant solution (it detects form elements changes in real time):
var formChanged = false;
$('#my-div form').on('keyup change paste', 'input, select, textarea', function(){
formChanged = true;
});
For a form you could serialize the contents on load then compare serialization at a later time, e.g.:
$(function(){
var initdata=$('form').serialize();
$('form').submit(function(e){
e.preventDefault();
var nowdata=$('form').serialize();
if(initdata==nowdata) console.log('nothing changed'); else console.log('something changed');
// save
initdata=nowdata;
$.post('settings.php',nowdata).done(function(){
console.log('saved');
});
});
});
Note this requires form elements to have a name attribute.
Just to clarify because the question is "within a certain fieldset/div":
var somethingChanged = false;
$(document).ready(function() {
$('fieldset > input').change(function() {
somethingChanged = true;
});
});
or
var somethingChanged = false;
$(document).ready(function() {
$('div > input').change(function() {
somethingChanged = true;
});
});
You can give the fieldset or div an ID and bind the change event to it ... the event should propagate from the inner children.
var somethingChanged = false;
$('#fieldset_id').change(function(e)
{
// e.target is the element which triggered the event
// console.log(e.target);
somethingChanged = true;
});
Additionally if you wanted to have a single event listening function you could put the change event on the form and then check which fieldset changed:
$('#form_id').change(function(e)
{
var changedFieldset = $(e.target).parents('fieldset');
// do stuff
});
I came up with this piece of code in CoffeeScript (not really field tested, yet):
Add class 'change_warning' to forms that should be watched for changes.
Add class 'change_allowed' to the save button.
change_warning.coffee:
window.ChangeWarning = {
save: ->
$(".change_warning").each (index,element) ->
$(element).data('serialized', $(element).serialize())
changed: (element) ->
$(element).serialize() != $(element).data('serialized')
changed_any: ->
$.makeArray($(".change_warning").map (index,element) -> ChangeWarning.changed(element)).some (f)->f
# AKA $(".change_warning").any (element) -> ChangeWarning.changed(element)
# But jQuery collections do not know the any/some method, yet (nor are they arrays)
change_allowed: ->
ChangeWarning.change_allowed_flag = true
beforeunload: ->
unless ChangeWarning.change_allowed_flag or not ChangeWarning.changed_any()
"You have unsaved changes"
}
$ ->
ChangeWarning.save()
$(".change_allowed").bind 'click', -> ChangeWarning.change_allowed()
$(window).bind 'beforeunload', -> ChangeWarning.beforeunload()
An alternative to Dw7's answer if you only want the fields inside a fieldset then you can call serialize() on its input values. Note: serialize() will not pickup any elements that do not have a "name" attribute. This will work for select tags as well.
var initialValues = $('#your-fieldset :input').serialize();
$('form').submit(function(e) {
e.preventDefault();
var currentValues = $('#your-fieldset :input').serialize();
if (currentValues == initialValues) {
// Nothing has changed
alert('Nothing was changed');
}
else {
this.submit();
}
});
.live is now deprecated and replaced by .on:
var confirmerSortie = false;
$(document).on('change', 'select', function() {
confirmerSortie = true;
});
$(document).on('change keypress', 'input', function() {
confirmerSortie = true;
});
$(document).on('change keypress', 'textarea', function() {
confirmerSortie = true;
});
The following solution worked for me:
$("#myDiv :input").change(function() { $("#myDiv").data("changed",true);});
}
if($("#myDiv").data("changed")) {
console.log('Form data changed hence proceed to submit');
}
else {
console.log('No change detected!');
}
Thanks

Categories