More efficient method than .empty() to avoid duplicates when populating <ul> - javascript

I am creating a form. I am trying to build a list which is dynamically populated with items representing valueless input fields from this form. I have created a simplified version in a fiddle to illustrate my issue. Essentially, I have the error list being created as I want. It successfully counts blank input fields and updates this count when data is entered or deleted and populates the list accordingly. However, to avoid duplicate entries in this list I am currently emptying it on each call. This seems fairly inefficient and, as you see on the fiddle, it runs it's empty and build functions immediately after a blur, so if an input is in focus and one attempts to click an error link, it takes a couple of clicks before it scrolls to the empty input in question. So my question is, is there another way I can avoid duplicate entries and it run fast enough so that it can fun the scroll function on click immediately from focus?
I hope this is clear enough. I added some white space to allow for a scroll effect so you can witness the multiple clicks required before the scroll occurs).
$(function(){
var submit = $('input[type="submit"]');
var monday = $("input[name='MONDAY']");
var tuesday = $("input[name='TUESDAY']");
var wednesday = $("input[name='WEDNESDAY']");
$('input').eq(0).focus();
// disable submit
submit.attr("disabled", true);
// call formValidate on load and on input blur
formValidate();
$('input').blur(function(){
formValidate();
});
function formValidate(){
// inputs
monday = $("input[name='MONDAY']");
tuesday = $("input[name='TUESDAY']");
wednesday = $("input[name='WEDNESDAY']");
// store values of inputs in vars
var inputCheck = {
'monday': monday.val(),
'tuesday': tuesday.val(),
'wednesday': wednesday.val()
};
var myErrors = [];
myErrors.splice(0,myErrors.length);
// if input has no value, add to error array
$.each( inputCheck, function(key, value){
if (value === '') {
myErrors.push(key);
}
});
// if any errors, create li for each
if (myErrors.length > 0 ) {
// display errors div
$('.errors').addClass('exist');
$('.errors').find('span').html(myErrors.length);
// empty ul to avoid duplicate entries
$('.error-box-item ul').empty();
// populate ul with any errors
$.each(myErrors, function(i) {
var li = $('<li>');
var link = myErrors[i].replace("-", " ");
var formattedLink = toTitleCase(link);
var tag = "#err-" + myErrors[i];
var a = $('<a></a>').attr("href", tag);
a.append(formattedLink);
li.append(a);
$('.error-box-item ul').append(li);
});
} else {
// if no errors, hide error info and
// enable the submit button
$('.errors').removeClass('exist');
submit.attr("disabled", false);
}
} // end validate form
function toTitleCase(str) {
return str.replace(/(?:^|\s)\w/g, function(match) {
return match.toUpperCase();
});
}
// scroll to link
$('a[href*="#"]:not([href="#"])').click(function() {
if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {
var target = $(this.hash);
target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
if (target.length) {
$('html, body').animate({
scrollTop: target.offset().top - 50
}, 500);
return false;
}
}
}); // end scroll
}); // end ready
.errors {
display: none;
}
.errors.exist {
display: block;
}
.demo-space {
height: 150px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form>
<p>Monday:</p>
<input id="err-monday" type="number" name="MONDAY">
<p>Tuesday:</p>
<input id="err-tuesday" type="number" name="TUESDAY">
<p>Wednesday:</p>
<input id="err-wednesday" type="number" name="WEDNESDAY">
<p>
<input type="submit" name="submit" value="submit">
</p>
</form>
<div class="demo-space"></div>
<div class="errors">
You have <span></span> errors:
<div class="error-box-item">
<ul class="list">
</ul>
</div>
</div>
<div class="demo-space"></div>

Related

How to manipulate list element on Enter key press

I want to allow the editing of an ordered list using contenteditable.
When the user changes or adds in a new element in the list I want to be able to manipulate the text (e.g. wrap in span tag, replace text etc).
I've created a listener for the Enter key and can get the last list element value.
I've tried to change this and replace with the new value. However this populates the new list element created on the enter press.
<div>
<ol contenteditable=true class="editor">
<li><br></li>
</ol>
</div>
$('.editor' ).on('keydown .editable', function(e){
if ( e.keyCode === 13 ) {
var insertText = "<span>"+e.target.lastElementChild.innerText+"</span>";
e.target.lastElementChild.innerHTML = insertText;
return true
}
});
What is the best way to implement this functionality for new entries anywhere in the list not just the end? Open to Jquery solutions
example jsfiddle
You could use a MutationObserver to detect when a child is added to your list, and then update the previousSibling to wrap it in a <span>:
function subscriber(mutations) {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
const prev = node.previousSibling;
if (prev) {
prev.innerHTML = `<span>${prev.innerHTML.replace(/<br>$/, '')}</span>`;
}
});
});
}
const observer = new MutationObserver(subscriber);
observer.observe(document.querySelector('ol[contenteditable]'), { childList: true });
.editor span::after {
content: '😀';
}
<ol contenteditable class="editor">
<li>First li</li>
</ol>
You could bind your logic to the last li, and perform your logic from the events it emits.
$('.editor .last-item')
.on('click', function(e){
// clear out the html so the user can just type
e.target.innerHTML = '';
})
.on('keydown', function(e){
if (e.keyCode === 13) {
// ignore the enter key so it doesn't insert a new like
e.preventDefault();
// create the new li before the last one
$('<li>'+ e.target.innerHTML.trim() +'</li>').insertBefore(e.target);
// clear out the html so the user can keep entering items
e.target.innerHTML = '';
}
})
.on('blur', function(e){
// if the user leaves the field, change it back to the instructional text
e.target.innerHTML = 'Click to enter New List Item';
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<ol class="editor">
<li class="last-item" contenteditable=true>Click to enter New List Item</li>
</ol>
</div>
Instead of taking action when an edit happens, you could set an interval that will modify the html of the li elements as desired.
setInterval(function(){
$('.editor' ).find('li').each(function(){
if ($(this).html().indexOf('span')==-1){
$(this).html('<span>' + $(this).html() + '</span>');
}
});
}, 200);
hello you might wanna check this let me give you a heads up. instead of using the
lastchild.innerHTML
replace it with
nextSibling.innerHTML
like this
$('.editor' ).on('keydown .editable', function(e){
if ( e.keyCode === 13 ) {
var insertText = "<span>"+e.target.lastElementChild.innerText+"</span>";
e.target.nextSibling.innerHTML = insertText;
return true
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<ol contenteditable=true class="editor">
<li><br></li>
</ol>
</div>
$('.editor' ).on('keydown .editable', function(e){
if ( e.keyCode === 13 ) {
var insertText = "<span>"+e.target.lastElementChild.innerText+"</span>";
e.target.nextSibling.innerHTML = insertText;
return true
}
});

"label.error" seems to always be present on every bootstrap tab

I can't figure out why $('label.error') is showing up on every bootstrap tab when that particular element should only show on 1 tab. I have a field that's not passing validation on a bootstrap tab and thus a label with class error is being appended the DOM on the violating field. But, I can't seem to get my code to land on the specific tab that has the violating field. What am I doing wrong in the code below? isErrorPresent should only be true on 1 particular tab but EVERY tab is showing it to be true...
$("#" + formId).validate({ignore:""}).form(); // ignore:"" allows for hidden fields to be validated as well
$(".tab-content").find("div.tab-pane").each(function (index, tab) {
var id = $(tab).attr("id");
$('a[href="#' + id + '"]').click();
alert('processing tab ' + id);
var isErrorPresent = $('div.tab-pane, div.active').find('label.error').length > 0;
alert(isErrorPresent);
// if (isErrorPresent) {
// alert("label.error detected...");
// hasError = true;
// return false; // Break .each loop and return to page
// }
});
Without seeing more markup, this is what I am thinking:
You are doing this:
var isErrorPresent = $('div.tab-pane, div.active').find('label.error').length > 0;
The selector has a comma in it, meaning you want to checking on div.tab-pane && div.active
Is that what you wanted? Maybe you meant to do this (no comma and no space):
var isErrorPresent = $('div.tab-pane.active').find('label.error').length > 0;
Figured it out with #Red2678's help (thanks)...here's what's working for me now...
// Check all tabs for errors & show first tab that has errors
$(".tab-content").find("div.tab-pane").each(function (index, tab) {
var id = $(tab).attr("id");
$('a[href="#' + id + '"]').click();
$("#" + formId).validate().form();
var activeTab = $('div.tab-pane.active');
var isErrorPresent = false;
if ($(activeTab).find('label.error').length > 0) {
isErrorPresent = $(activeTab).find('label.error').css('display') !== 'none';
}
if (isErrorPresent) {
hasError = true;
return false; // Break .each loop and return to page
}
});

Why does my counter not work in the field like I would expect

I have implemented a JS counter in my app. I have 2 form fields that my two different counters should work for. A #post_title and a #body-field.
This is my JS:
counter = function() {
var title_value = $('#post_title').val();
var body_value = $('#body-field').val();
if (title_value.length == 0) {
$('#wordCountTitle').html(0);
return;
}
if (body_value.length == 0) {
$('#wordCountBody').html(0);
return;
}
var regex = /\s+/gi;
var wordCountTitle = title_value.trim().replace(regex, ' ').split(' ').length;
var wordCountBody = body_value.trim().replace(regex, ' ').split(' ').length;
$('#wordCountTitle').html(wordCountTitle);
$('#wordCountBody').html(wordCountBody);
};
$(document).on('ready page:load', function () {
$('#count').click(counter);
$('#post_title, #body-field').on('change keydown keypress keyup blur focus', counter);
});
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body>
<label for="title">Title</label>
<textarea id="post_title" placeholder="Enter Title"></textarea>
<span id="wordCountTitle">0</span> words<br/>
<label for="report">Report</label>
<textarea id="body-field"placeholder="Provide all the facts." rows="4">
</textarea><br />
<span id="wordCountBody">0</span> / 150 words
</body>
</html>
The seemingly stray $(document).ready(ready); corresponds to a var ready = function() called earlier in the file that I left out for brevity purposes. But I left the document.ready() call in the order that it appears just incase it could be causing an issue.
So the issue I am having is, whenever you click on the #post_title field and enter words the counter does not update. But as soon as I click on the #body-field and start typing not only does the counter for the #body-field work and start updating immediately, but the counter for the #post_title starts working too and shows the correct amount of words in that field.
What could be causing this?
Edit 1
Just playing with that code snippet I realized that the error exists in another state too. If you just add text to the 2nd field first (i.e. the #body-field) before entering in the title...the counter for the body-field won't increment. It will only update AFTER you start entering a title in the #post_title. So they are both linked somehow.
You should not have the counter function check and perform operations on both fields. The counter function should do exactly the same operation, by utilizing jquery's this keyword inside it, or by taking an event parameter and using that as an alternative, with event.target.
Here's the refactor:
var counter = function(event) {
var fieldValue = $(this).val();
var wc = fieldValue.trim().replace(regex, ' ').split(' ').length;
var regex = /\s+/gi;
var $wcField = $(this)[0] === $('#post_title')[0] ? $('#wordCountTitle') : $('#wordCountBody');
if (fieldValue.length === 0) {
$wcField.html('');
return;
}
$wcField.html(wc);
};
$(document).on('ready page:load', function () {
$('#post_title, #body-field').on('change keyup paste', counter);
});
JSBin playground
Also, I am not entirely sure why you are listening to that many events on those textareas, when change keyup paste ought to do it.
the error you're having is because of these 2 blocks of code
if (title_value.length == 0) {
$('#wordCountTitle').html(0);
return;
}
if (body_value.length == 0) {
$('#wordCountBody').html(0);
return;
}
This means that in order for the counters to run, both title and body should have some value. Just remove the return on both and it should work.
EDIT
I think removing both if blocks will also give you the same behavior you want. If you want to have both if blocks, you'll have to separate the counter for the title and body.
EDIT 2
here's a simpler implementation of counter function.
counter = function() {
var title_value = $('#post_title').val();
var body_value = $('#body-field').val();
$('#wordCountTitle').html(title_value.split(' ').length);
$('#wordCountBody').html(body_value.split(' ').length);
}

What to do when $('#div').trigger('click'); doesn't trigger (Closed, lost interest)

I'm writing a userscript that injects a custom javascript into the head of a page. Easy peasy
// ==UserScript==
// #name *** (BLOCKED DUE TO NDA)
// #namespace *** (BLOCKED DUE TO NDA)
// #description *** (BLOCKED DUE TO NDA)
// #include *** (BLOCKED DUE TO NDA)
// #author Aaron K. Henderson
// #version 1.0
// ==/UserScript==
var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
script.src= '***.js';
head.appendChild(script);
The .js I inject makes use of the jQuery already being used on the page to make some css changes as well as automate some mundane tasks.
What I want to happen is when I fire $('#approve-all-button').click() and it detects a username that it applies the RED background to, for the script to also click the Delete "button" as well.
The code on the original site for the buttons looks like:
<div class="jquery-buttongroup ui-buttonset">
<input type="radio" id="form-0-status_A" name="form-0-status" value="A" class="ui-helper-hidden-accessible">
<label for="form-0-status_A" aria-pressed="false" class="ui-button ui-widget ui-state-default ui-button-text-only ui-corner-left" role="button" aria-disabled="false">
<span class="ui-button-text">Approved</span>
</label>
<input type="radio" id="form-0-status_I" name="form-0-status" value="I" checked="checked" class="ui-helper-hidden-accessible">
<label for="form-0-status_I" class="ui-state-active ui-button ui-widget ui-state-default ui-button-text-only" aria-pressed="true" role="button" aria-disabled="false">
<span class="ui-button-text">Inactive</span>
</label>
<input type="radio" id="form-0-status_D" name="form-0-status" value="D" class="ui-helper-hidden-accessible">
<label for="form-0-status_D" aria-pressed="false" class="ui-button ui-widget ui-state-default ui-button-text-only ui-corner-right" role="button" aria-disabled="false">
<span class="ui-button-text">Deleted</span>
</label>
</div>
There are 100 iteration of the above button code. Each iterations set of IDs has a variable that increase by 1 for each iteration eg.
id="form-0-status_D"
id="form-1-status_D"
id="form-2-status_D"
id="form-3-status_D"
id="form-4-status_D"
The ID is being found by $('#form-'+i+'-status_D')
$(document).ready(function() {
// Rename Approve All Button
$('#approve-all-button span').text('Scan + Detect');
// The Magic
$('#approve-all-button').click(function(i) {
i.preventDefault();
var Current_Name = '';
// Loop through TR
$('tr').each(function(i) {
if (i > 0) {
var _self = $(this)
// Get Current Username in Loop
Current_Name = _self.children('.username').text();
// Apply Default Color to All (Green)
_self.css('background-color', '#0AFE47');
// Apply Approved Class to All
_self.addClass('AddApproved');
// Hide Creation Date / Last Login
_self.children('.nowrap').css('opacity','.1').css('background-color','white');
// Get Current Username Length
var nlen = Current_Name.length;
// If Name Length is <= 3 or >= 15 Apply Color (Red)
if ((nlen <= 3) || (nlen >= 15)){
_self.css('background-color','#FF7575').
addClass('AddDeleted').
removeClass('AddApproved').
removeClass('AddInactive');
$(_self).children('#form-'+i+'-status_D').trigger('click');
}
var nDigits = 0;
for ( var t=0; t<nlen; t++) {
var chr = Current_Name.charAt(t);
if (chr >= "0" && chr <= "9") nDigits++;
}
var charcount = nlen - nDigits;
if ((nDigits >=6) || (charcount < 3) || (nDigits == nlen)) {
_self.css('background-color','#FF7575').
addClass('AddDeleted').
removeClass('AddApproved').
removeClass('AddInactive');
$(_self).children('#form-'+i+'-status_D').trigger('click');
}
}
});
});
// On Button Click, Change Background and Add/Remove class
$('label').click(function(i) {
var _self = $(this)
var button = _self.attr('for');
var status = button.substring(button.lastIndexOf('-') + 1);
if (status == 'status_D') {
_self.closest('tr').css('background-color','#FF7575').
addClass('AddDeleted').
removeClass('AddApproved').
removeClass('AddInactive');
} else if (status == 'status_A') {
_self.closest('tr').css('background-color','#0AFE47').
addClass('AddApproved').
removeClass('AddInactive').
removeClass('AddDeleted');
} else if (status == 'status_I') {
_self.closest('tr').css('background-color','#0AFE47').
addClass('AddInactive').
removeClass('AddApproved').
removeClass('AddDeleted');
}
});
});
Again, what I want to happen is for when I click the Approve All div for it to loop though all of the and apply the default event (which is to set it to approve) unless a particular rule (username <= 3 || >= 15 || etc) is triggered. In which case I would like the Delete button to be selected in stead.
I am attempting to click the delete button with:
$('#form-'+i+'-status_D').trigger('click');
However, that does not seem to work.
The problem(s) I seem to be having is the I am adding the click event to already has another click event tied to it that appears to supersede my event. I've tried to unbind the existing event; which does unbind successfully, but with it unbound I am unable to .trigger
Is trigger('click') failing in this instance? Or do I have a hiccup in my code some where.
While sifting though the source on the page I did find this linked javascript page
var Actions = {
init: function() {
var selectAll = document.getElementById('action-toggle');
if (selectAll) {
selectAll.style.display = 'inline';
addEvent(selectAll, 'click', function() {
Actions.checker(selectAll.checked);
});
}
var changelistTable = document.getElementsBySelector('#changelist table')[0];
if (changelistTable) {
addEvent(changelistTable, 'click', function(e) {
if (!e) { var e = window.event; }
var target = e.target ? e.target : e.srcElement;
if (target.nodeType == 3) { target = target.parentNode; }
if (target.className == 'action-select') {
var tr = target.parentNode.parentNode;
Actions.toggleRow(tr, target.checked);
}
});
}
},
toggleRow: function(tr, checked) {
if (checked && tr.className.indexOf('selected') == -1) {
tr.className += ' selected';
} else if (!checked) {
tr.className = tr.className.replace(' selected', '');
}
},
checker: function(checked) {
var actionCheckboxes = document.getElementsBySelector('tr input.action-select');
for(var i = 0; i < actionCheckboxes.length; i++) {
actionCheckboxes[i].checked = checked;
Actions.toggleRow(actionCheckboxes[i].parentNode.parentNode, checked);
}
}
};
addEvent(window, 'load', Actions.init);
When a Label/Input Radio is clicked Google Chrome (via inspect this element) tells me that script is called. (I dont know if that information helps any, but reading it is beyond my scope with the limited knowledge I have of jQuery)
Also to clear up any confusion I am simply adding the AddApproved/AddInactive/AddDeleted class for tallying purposes.
I have yet to implement the counter for this, but I already know how I am going to make that work.
P.S. This is my first time using jQuery, so this is as much as a tool for my place of employment as it is a learning experience. If some of the code seems nOOby I do apologize.
trigger() works when you bind an event to an element. Quoting from jQuery documentation site:
Any event handlers attached with .bind() or one of its shortcut
methods are triggered when the corresponding event occurs. They can be
fired manually, however, with the .trigger() method
In your case, subscribe to the click event on #form-1-status_D and write necessary code there and now you call call trigger('click') along.
Keep in mind that, as you are changing your div ID, subscribing once to that div would do it.
You have this:
var _self = $(this);
$(_self).children('#form-'+i+'-status_D').trigger('click');
The second line should be:
_self.children('#form-'+i+'-status_D').trigger('click');
Your variable _self already references jQuery by $(this) and so should not be used like $(_self) which would be the same as doing $($(...))

Enter key press behaves like a Tab in Javascript

I'm looking to create a form where pressing the enter key causes focus to go to the "next" form element on the page. The solution I keep finding on the web is...
<body onkeydown="if(event.keyCode==13){event.keyCode=9; return event.keyCode}">
Unfortunately, that only seems to work in IE. So the real meat of this question is if anybody knows of a solution that works for FF and Chrome? Additionally, I'd rather not have to add onkeydown events to the form elements themselves, but if that's the only way, it will have to do.
This issue is similar to question 905222, but deserving of it's own question in my opinion.
Edit: also, I've seen people bring up the issue that this isn't good style, as it diverges from form behavior that users are used to. I agree! It's a client request :(
I used the logic suggested by Andrew which is very effective. And this is my version:
$('body').on('keydown', 'input, select', function(e) {
if (e.key === "Enter") {
var self = $(this), form = self.parents('form:eq(0)'), focusable, next;
focusable = form.find('input,a,select,button,textarea').filter(':visible');
next = focusable.eq(focusable.index(this)+1);
if (next.length) {
next.focus();
} else {
form.submit();
}
return false;
}
});
KeyboardEvent's keycode (i.e: e.keycode) depreciation notice :- https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
The simplest vanilla JS snippet I came up with:
document.addEventListener('keydown', function (event) {
if (event.keyCode === 13 && event.target.nodeName === 'INPUT') {
var form = event.target.form;
var index = Array.prototype.indexOf.call(form, event.target);
form.elements[index + 1].focus();
event.preventDefault();
}
});
Works in IE 9+ and modern browsers.
Map [Enter] key to work like the [Tab] key
I've rewritten Andre Van Zuydam's answer, which didn't work for me, in jQuery. This caputures both Enter and Shift+Enter. Enter tabs forward, and Shift+Enter tabs back.
I've also rewritten the way self is initialized by the current item in focus. The form is also selected that way. Here's the code:
// Map [Enter] key to work like the [Tab] key
// Daniel P. Clark 2014
// Catch the keydown for the entire document
$(document).keydown(function(e) {
// Set self as the current item in focus
var self = $(':focus'),
// Set the form by the current item in focus
form = self.parents('form:eq(0)'),
focusable;
// Array of Indexable/Tab-able items
focusable = form.find('input,a,select,button,textarea,div[contenteditable=true]').filter(':visible');
function enterKey(){
if (e.which === 13 && !self.is('textarea,div[contenteditable=true]')) { // [Enter] key
// If not a regular hyperlink/button/textarea
if ($.inArray(self, focusable) && (!self.is('a,button'))){
// Then prevent the default [Enter] key behaviour from submitting the form
e.preventDefault();
} // Otherwise follow the link/button as by design, or put new line in textarea
// Focus on the next item (either previous or next depending on shift)
focusable.eq(focusable.index(self) + (e.shiftKey ? -1 : 1)).focus();
return false;
}
}
// We need to capture the [Shift] key and check the [Enter] key either way.
if (e.shiftKey) { enterKey() } else { enterKey() }
});
The reason textarea
is included is because we "do" want to tab into it. Also, once in, we don't want to stop the default behavior of Enter from putting in a new line.
The reason a and button
allow the default action, "and" still focus on the next item, is because they don't always load another page. There can be a trigger/effect on those such as an accordion or tabbed content. So once you trigger the default behavior, and the page does its special effect, you still want to go to the next item since your trigger may have well introduced it.
Thank you for the good script.
I have just added the shift event on the above function to go back between elements, I thought someone may need this.
$('body').on('keydown', 'input, select, textarea', function(e) {
var self = $(this)
, form = self.parents('form:eq(0)')
, focusable
, next
, prev
;
if (e.shiftKey) {
if (e.keyCode == 13) {
focusable = form.find('input,a,select,button,textarea').filter(':visible');
prev = focusable.eq(focusable.index(this)-1);
if (prev.length) {
prev.focus();
} else {
form.submit();
}
}
}
else
if (e.keyCode == 13) {
focusable = form.find('input,a,select,button,textarea').filter(':visible');
next = focusable.eq(focusable.index(this)+1);
if (next.length) {
next.focus();
} else {
form.submit();
}
return false;
}
});
This worked for me:
$(document).on('keydown', ':tabbable', function(e) {
if (e.key === "Enter") {
e.preventDefault();
var $canfocus = $(':tabbable:visible');
var index = $canfocus.index(document.activeElement) + 1;
if (index >= $canfocus.length) index = 0;
$canfocus.eq(index).focus();
}
});
Changing this behaviour actually creates a far better user experience than the default behaviour implemented natively. Consider that the behaviour of the enter key is already inconsistent from the user's point of view, because in a single line input, enter tends to submit a form, while in a multi-line textarea, it simply adds a newline to the contents of the field.
I recently did it like this (uses jQuery):
$('input.enterastab, select.enterastab, textarea.enterastab').live('keydown', function(e) {
if (e.keyCode==13) {
var focusable = $('input,a,select,button,textarea').filter(':visible');
focusable.eq(focusable.index(this)+1).focus();
return false;
}
});
This is not terribly efficient, but works well enough and is reliable - just add the 'enterastab' class to any input element that should behave in this way.
I reworked the OPs solution into a Knockout binding and thought I'd share it. Thanks very much :-)
Here's a Fiddle
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" type="text/javascript"></script>
<script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js" type="text/javascript"></script>
</head>
<body>
<div data-bind="nextFieldOnEnter:true">
<input type="text" />
<input type="text" />
<select>
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
<option value="mercedes">Mercedes</option>
<option value="audi">Audi</option>
</select>
<input type="text" />
<input type="text" />
</div>
<script type="text/javascript">
ko.bindingHandlers.nextFieldOnEnter = {
init: function(element, valueAccessor, allBindingsAccessor) {
$(element).on('keydown', 'input, select', function (e) {
var self = $(this)
, form = $(element)
, focusable
, next
;
if (e.keyCode == 13) {
focusable = form.find('input,a,select,button,textarea').filter(':visible');
var nextIndex = focusable.index(this) == focusable.length -1 ? 0 : focusable.index(this) + 1;
next = focusable.eq(nextIndex);
next.focus();
return false;
}
});
}
};
ko.applyBindings({});
</script>
</body>
</html>
Here is an angular.js directive to make enter go to the next field using the other answers as inspiration. There is some, perhaps, odd looking code here because I only use the jQlite packaged with angular. I believe most of the features here work in all browsers > IE8.
angular.module('myapp', [])
.directive('pdkNextInputOnEnter', function() {
var includeTags = ['INPUT', 'SELECT'];
function link(scope, element, attrs) {
element.on('keydown', function (e) {
// Go to next form element on enter and only for included tags
if (e.keyCode == 13 && includeTags.indexOf(e.target.tagName) != -1) {
// Find all form elements that can receive focus
var focusable = element[0].querySelectorAll('input,select,button,textarea');
// Get the index of the currently focused element
var currentIndex = Array.prototype.indexOf.call(focusable, e.target)
// Find the next items in the list
var nextIndex = currentIndex == focusable.length - 1 ? 0 : currentIndex + 1;
// Focus the next element
if(nextIndex >= 0 && nextIndex < focusable.length)
focusable[nextIndex].focus();
return false;
}
});
}
return {
restrict: 'A',
link: link
};
});
Here's how I use it in the app I'm working on, by just adding the pdk-next-input-on-enter directive on an element. I am using a barcode scanner to enter data into fields, the default function of the scanner is to emulate a keayboard, injecting an enter key after typing the data of the scanned barcode.
There is one side-effect to this code (a positive one for my use-case), if it moves focus onto a button, the enter keyup event will cause the button's action to be activated. This worked really well for my flow as the last form element in my markup is a button that I want activated once all the fields have been "tabbed" through by scanning barcodes.
<!DOCTYPE html>
<html ng-app=myapp>
<head>
<script src="angular.min.js"></script>
<script src="controller.js"></script>
</head>
<body ng-controller="LabelPrintingController">
<div class='.container' pdk-next-input-on-enter>
<select ng-options="p for p in partNumbers" ng-model="selectedPart" ng-change="selectedPartChanged()"></select>
<h2>{{labelDocument.SerialNumber}}</h2>
<div ng-show="labelDocument.ComponentSerials">
<b>Component Serials</b>
<ul>
<li ng-repeat="serial in labelDocument.ComponentSerials">
{{serial.name}}<br/>
<input type="text" ng-model="serial.value" />
</li>
</ul>
</div>
<button ng-click="printLabel()">Print</button>
</div>
</body>
</html>
Try this...
$(document).ready(function () {
$.fn.enterkeytab = function () {
$(this).on('keydown', 'input,select,text,button', function (e) {
var self = $(this)
, form = self.parents('form:eq(0)')
, focusable
, next
;
if (e.keyCode == 13) {
focusable = form.find('input,a,select').filter(':visible');
next = focusable.eq(focusable.index(this) + 1);
if (next.length) {
//if disable try get next 10 fields
if (next.is(":disabled")){
for(i=2;i<10;i++){
next = focusable.eq(focusable.index(this) + i);
if (!next.is(":disabled"))
break;
}
}
next.focus();
}
return false;
}
});
}
$("form").enterkeytab();
});
I've had a similar problem, where I wanted to press + on the numpad to tab to the next field. Now I've released a library that I think will help you.
PlusAsTab: A jQuery plugin to use the numpad plus key as a tab key equivalent.
Since you want enter/↵ instead, you can set the options. Find out which key you want to use with the jQuery event.which demo.
JoelPurra.PlusAsTab.setOptions({
// Use enter instead of plus
// Number 13 found through demo at
// https://api.jquery.com/event.which/
key: 13
});
// Matches all inputs with name "a[]" (needs some character escaping)
$('input[name=a\\[\\]]').plusAsTab();
You can try it out yourself in the PlusAsTab enter as tab demo.
function return2tab (div)
{
document.addEventListener('keydown', function (ev) {
if (ev.key === "Enter" && ev.target.nodeName === 'INPUT') {
var focusableElementsString = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]';
let ol= div.querySelectorAll(focusableElementsString);
for (let i=0; i<ol.length; i++) {
if (ol[i] === ev.target) {
let o= i<ol.length-1? ol[i+1]: o[0];
o.focus(); break;
}
}
ev.preventDefault();
}
});
}
I have it working in only JavaScript. Firefox won't let you update the keyCode, so all you can do is trap keyCode 13 and force it to focus on the next element by tabIndex as if keyCode 9 was pressed. The tricky part is finding the next tabIndex. I have tested this only on IE8-IE10 and Firefox and it works:
function ModifyEnterKeyPressAsTab(event)
{
var caller;
var key;
if (window.event)
{
caller = window.event.srcElement; //Get the event caller in IE.
key = window.event.keyCode; //Get the keycode in IE.
}
else
{
caller = event.target; //Get the event caller in Firefox.
key = event.which; //Get the keycode in Firefox.
}
if (key == 13) //Enter key was pressed.
{
cTab = caller.tabIndex; //caller tabIndex.
maxTab = 0; //highest tabIndex (start at 0 to change)
minTab = cTab; //lowest tabIndex (this may change, but start at caller)
allById = document.getElementsByTagName("input"); //Get input elements.
allByIndex = []; //Storage for elements by index.
c = 0; //index of the caller in allByIndex (start at 0 to change)
i = 0; //generic indexer for allByIndex;
for (id in allById) //Loop through all the input elements by id.
{
allByIndex[i] = allById[id]; //Set allByIndex.
tab = allByIndex[i].tabIndex;
if (caller == allByIndex[i])
c = i; //Get the index of the caller.
if (tab > maxTab)
maxTab = tab; //Get the highest tabIndex on the page.
if (tab < minTab && tab >= 0)
minTab = tab; //Get the lowest positive tabIndex on the page.
i++;
}
//Loop through tab indexes from caller to highest.
for (tab = cTab; tab <= maxTab; tab++)
{
//Look for this tabIndex from the caller to the end of page.
for (i = c + 1; i < allByIndex.length; i++)
{
if (allByIndex[i].tabIndex == tab)
{
allByIndex[i].focus(); //Move to that element and stop.
return;
}
}
//Look for the next tabIndex from the start of page to the caller.
for (i = 0; i < c; i++)
{
if (allByIndex[i].tabIndex == tab + 1)
{
allByIndex[i].focus(); //Move to that element and stop.
return;
}
}
//Continue searching from the caller for the next tabIndex.
}
//The caller was the last element with the highest tabIndex,
//so find the first element with the lowest tabIndex.
for (i = 0; i < allByIndex.length; i++)
{
if (allByIndex[i].tabIndex == minTab)
{
allByIndex[i].focus(); //Move to that element and stop.
return;
}
}
}
}
To use this code, add it to your html input tag:
<input id="SomeID" onkeydown="ModifyEnterKeyPressAsTab(event);" ... >
Or add it to an element in javascript:
document.getElementById("SomeID").onKeyDown = ModifyEnterKeyPressAsTab;
A couple other notes:
I only needed it to work on my input elements, but you could extend it to other document elements if you need to. For this, getElementsByClassName is very helpful, but that is a whole other topic.
A limitation is that it only tabs between the elements that you have added to your allById array. It does not tab around to the other things that your browser might, like toolbars and menus outside your html document. Perhaps this is a feature instead of a limitation. If you like, trap keyCode 9 and this behavior will work with the tab key too.
You can use my code below, tested in Mozilla, IE, and Chrome
// Use to act like tab using enter key
$.fn.enterkeytab=function(){
$(this).on('keydown', 'input, select,', function(e) {
var self = $(this)
, form = self.parents('form:eq(0)')
, focusable
, next
;
if (e.keyCode == 13) {
focusable = form.find('input,a,select,button').filter(':visible');
next = focusable.eq(focusable.index(this)+1);
if (next.length) {
next.focus();
} else {
alert("wd");
//form.submit();
}
return false;
}
});
}
How to Use?
$("#form").enterkeytab(); // enter key tab
If you can I would reconsider doing this: the default action of pressing <Enter> while in a form submits the form and anything you do to change this default action / expected behaviour could cause some usability issues with the site.
Vanilla js with support for Shift + Enter and ability to choose which HTML tags are focusable. Should work IE9+.
onKeyUp(e) {
switch (e.keyCode) {
case 13: //Enter
var focusableElements = document.querySelectorAll('input, button')
var index = Array.prototype.indexOf.call(focusableElements, document.activeElement)
if(e.shiftKey)
focus(focusableElements, index - 1)
else
focus(focusableElements, index + 1)
e.preventDefault()
break;
}
function focus(elements, index) {
if(elements[index])
elements[index].focus()
}
}
Here's what I came up with.
form.addEventListener("submit", (e) => { //On Submit
let key = e.charCode || e.keyCode || 0 //get the key code
if (key = 13) { //If enter key
e.preventDefault()
const inputs = Array.from(document.querySelectorAll("form input")) //Get array of inputs
let nextInput = inputs[inputs.indexOf(document.activeElement) + 1] //get index of input after the current input
nextInput.focus() //focus new input
}
}
Many answers here uses e.keyCode and e.which that are deprecated.
Instead you should use e.key === 'Enter'.
Documentation: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
I'm sorry but I can't test these snippets just now. Will come back later after testing it.
With HTML:
<body onkeypress="if(event.key==='Enter' && event.target.form){focusNextElement(event); return false;}">
With jQuery:
$(window).on('keypress', function (ev)
{
if (ev.key === "Enter" && ev.currentTarget.form) focusNextElement(ev)
}
And with Vanilla JS:
document.addEventListener('keypress', function (ev) {
if (ev.key === "Enter" && ev.currentTarget.form) focusNextElement(ev);
});
You can take focusNextElement() function from here:
https://stackoverflow.com/a/35173443/3356679
Easiest way to solve this problem with the focus function of JavaScript as follows:
You can copy and try it # home!
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<input id="input1" type="text" onkeypress="pressEnter()" />
<input id="input2" type="text" onkeypress="pressEnter2()" />
<input id="input3" type="text"/>
<script type="text/javascript">
function pressEnter() {
// Key Code for ENTER = 13
if ((event.keyCode == 13)) {
document.getElementById("input2").focus({preventScroll:false});
}
}
function pressEnter2() {
if ((event.keyCode == 13)) {
document.getElementById("input3").focus({preventScroll:false});
}
}
</script>
</body>
</html>
I had a problem to use enter key instead of Tab in React js .The solution of anjana-silva is working fine and just some small issue for input date and autocomplete as I am using MUI . So I change it a bit and add arrow keys (left/right) as well .
install jquery using npm
npm install jquery --save
write the below in App.js If you want to have this behavior In the whole of your application
import $ from 'jquery';
useEffect(() => {
$('body').on('keydown', 'input, select,button', function (e) {
if (e.keyCode === 13 || e.keyCode === 39) {
var self = $(this), form = self.parents('form:eq(0)'), focusable, next;
focusable = form.find('input,a,select,button,textarea').filter(':visible:not([readonly]):enabled');
next = focusable.eq(focusable.index(this) + 1);
if (next.length) {
next.focus();
}
return false;
}
if (e.keyCode === 37) {
var self = $(this), form = self.parents('form:eq(0)'), focusable, prev;
focusable = form.find('input,a,select,button,textarea').filter(':visible:not([readonly]):enabled');
prev = focusable.eq(focusable.index(this) - 1);
if (prev.length) {
prev.focus();
}
return false;
}
});
}, []);
I had a simular need.
Here is what I did:
<script type="text/javascript" language="javascript">
function convertEnterToTab() {
if(event.keyCode==13) {
event.keyCode = 9;
}
}
document.onkeydown = convertEnterToTab;
</script>
In all that cases, only works in Chrome and IE, I added the following code to solve that:
var key = (window.event) ? e.keyCode : e.which;
and I tested the key value on if keycode equals 13
$('body').on('keydown', 'input, select, textarea', function (e) {
var self = $(this)
, form = self.parents('form:eq(0)')
, focusable
, next
;
var key = (window.event) ? e.keyCode : e.which;
if (key == 13) {
focusable = form.find('input,a,select,button,textarea').filter(':visible');
next = focusable.eq(focusable.index(this) + 1);
if (next.length) {
next.focus();
} else {
focusable.click();
}
return false;
}
});
$("#form input , select , textarea").keypress(function(e){
if(e.keyCode == 13){
var enter_position = $(this).index();
$("#form input , select , textarea").eq(enter_position+1).focus();
}
});
You could programatically iterate the form elements adding the onkeydown handler as you go. This way you can reuse the code.

Categories

Resources