Twitter Typeahead differentiate between events - javascript

I'm designing a system that allows users to annotate elements with either strings or terms from a vocabulary. I use Twitter typeahead for autocompletion and want to distinguish between an entered string and a term.
I am not able to figure out how to differentiate between the following situations:
a user pressing enter after coming up with its own value
a user pressing a down (or up) arrow (thereby selecting an autocompletion option) and pressing enter
The event listeners I wrote:
$("#itemInp").on('typeahead:select', function(event, term) {
console.log("save: term", term);
});
$("#itemInp").on('keyup', function(event) {
if(event.which == 13) {
var string = $("#itemInp").val();
console.log("save: string", string);
}
});
With the following HTML:
<input id="itemInp"><input>
The first listener catches all selected typeahead terms, allowing for proper saving of the term. Problem is, the second listener is also triggered in case a user presses down and enter, selecting a typeahead term, which is now also saved as a plain string. Is there a way to not trigger the second listener in case a typeahead suggestion is selected?
A fiddle with the code loaded:
https://jsfiddle.net/zban3vs6/1/
Edit:
I considered a number of hacks, but all come with their own problems:
Add a special character to the string shown in input at the moment it is selected, by adding custom Typeahead display: https://jsfiddle.net/2t9rzhwf/
This causes an additional character to be introduced, which is troublesome if a user presses the down arrow without pressing enter.
Attempt at sequencing the listeners, have the term listener trigger first, set a boolean to true, and filter on this boolean in the second listener. Don't like this way of filtering, since it introduces a delay.
The solution provided by #peter-clause, checking if the selected value is in the list of available items. But that way I can not keep track of whether the user intended to use the autocomplete option to select a term or explicitly is adding a plain string.

I had a similar problem. This is how I solved it.
var wasSelected = false;
$("#itemInp").on('typeahead:select', function(event, term) {
wasSelected = true;
console.log("save: term", term);
});
$("#itemInp").on('change', function(event) {
if(!wasSelected) {
var string = $("#itemInp").val();
console.log("save: string", string);
}
wasSelected = false;
});

A rather hacky solution, but you could check if the selected value is in the list of available items.
if (states.indexOf(string) == -1) {
//...
}
See https://jsfiddle.net/zban3vs6/2/

Related

Directive for restricting typing by Regex in AngularJS

I coded an angular directive for inhibiting typing from inputs by specifying a regex. In that directive I indicate a regex that will be used for allow the input data. Conceptually, it works fine, but there are two bugs in this solution:
In the first Plunker example the input must allow only numbers or numbers followed by a dot [.], or numbers followed by a dot followed by numbers with no more than four digits.
If I type a value '1.1111' and after that I go to the first digit and so type another digit (in order to get a value as '11.1111') , nothing happening. The bug is in the fact I use the expression elem.val() + event.key on my regex validator. I do not know how to get the whole
current value for a input on a keypress event;
The second one is the fact that some characters (grave, acute, tilde, circumflex) are being allowed on typing (press one of them more than once), althought the regex does not allow them.
What changes do I need to make in my code in order to get an effective type restriction by regex?
<html ng-app="app">
<head>
<script data-require="angularjs#1.6.4" data-semver="1.6.4" src="https://code.angularjs.org/1.6.4/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<h1>Restrict typing by RegExp</h1>
PATTERN 1 (^\d+$|^\d+[.]$|^\d+[.]\d{1,4}$) <input type="text" allow-typing="^\d+$|^\d+[.]$|^\d+[.]\d{1,4}$"/><br>
ONLY NUMBERS <input type="text" allow-typing="^[0-9]+$"/><br>
ONLY STRINGS <input type="text" allow-typing="^[a-zA-Z]+$"/>
</body>
</html>
Directive
angular.module('app', []).directive('allowTyping', function() {
return {
restrict: 'A',
link: function(scope, elem, attrs, ctrl) {
var regex = attrs.allowTyping;
elem.bind('keypress', function(event) {
var input = elem.val() + event.key;
var validator = new RegExp(regex);
if(!validator.test(input)) {
event.preventDefault();
return false;
}
});
}
};
});
If this were my code, I'd change tactics entirely: I would listen for input events instead of trying to micromanage the user interactions with the field.
The approach you are taking, in general, has problems. The biggest one is that keypress won't be emitted for all changes to the field. Notably,
It is not triggered by DELETE and BACKSPACE keys.
Input methods can bypass it. When you entered diacritics as diacritics, your code was not registering the change. In general, if the user is using an input method, there is no guarantee that each new character added to the field will result in a keypress event. It depends on the method the user has chosen.
keypress does not help when the user cuts from the field or pastes into the field.
You could add code to try to handle all the cases above, but it would get complex quick. You've already run into an issue with elem.val() + event.key because the keypress may not always be about a character inserted at the end of the field. The user may have moved the caret so you have to keep track of caret position. One comment suggested listening to keyup but that does not help with input methods or paste/cut events.
In contrast, the input event is generated when the value of the field changes, as the changes occur. All cases above are taken care of. This, for instance, would work:
elem.bind('input', function(event) {
var validator = new RegExp(regex);
elem.css("background-color", !validator.test(elem.val()) ? "red" : null);
});
This is a minimal illustration that you could plop into your fiddle to replace your current event handler. In a real application, I'd give the user a verbose error message rather than just change the color of the field and I'd create validator just once, outside the event handler, but this gives you the idea.
(There's also a change event but you do no want to use that. For text fields, it is generated when the focus leaves the field, which is much too late.)
See Plnkr Fixed as per your approach:
The explanation of why and the changes are explained below.
Side note: I would not implement it this way (use ngModel with $parsers and $formatters, e.g. https://stackoverflow.com/a/15090867/2103767) - implementing that is beyond the scope of your question. However I found a full implementation by regexValidate by Ben Lesh which will fit your problem domain:-
If I type a value '1.1111' and after that I go to the first digit and so type another digit (in order to get a value as '11.1111') , nothing happening.
because in your code below
var input = elem.val() + event.key;
you are assuming that the event.key is always appended at the end.
So how to get the position of the correct position and validate the the reconstructed string ? You can use an undocumented event.target.selectionStart property. Note even though you are not selecting anything you will have this populated (IE 11 and other browsers). See Plnkr Fixed
The second one is the fact that some characters (grave, acute, tilde, circumflex) are being allowed on typing (press one of them more than once), althought the regex does not allow them.
Fixed the regex - correct one below:
^[0-9]*(?:\.[0-9]{0,4})?$
So the whole thing looks as below
link: function(scope, elem, attrs, ctrl) {
var regex = attrs.allowTyping;
elem.bind('keypress', function(event) {
var pos = event.target.selectionStart;
var oldViewValue = elem.val();
var input = newViewValue(oldViewValue, pos, event.key);
console.log(input);
var validator = new RegExp(regex);
if (!validator.test(input)) {
event.preventDefault();
return false;
}
});
function newViewValue(oldViewValue, pos, key) {
if (!oldViewValue) return key;
return [oldViewValue.slice(0, pos), key, oldViewValue.slice(pos)].join('');
}
}
You specified 4 different patterns 3 different pattens in your regex separated by an alteration sign: ^\d+$|^\d+[.]$|^\d+[.]\d{1,4}$ - this will not fulfill the criteria of input must allow only numbers followed by a dot [.], followed by a number with no more than four digits. The bug "where nothing happens" occurs because the variable you are checking against is not what you think it is, check the screenshot on how you can inspect it, and what it is:
Can not reproduce.
You can change the event to keyup, so the test would run after every additional character is added.
It means you need to save the last valid input, so if the user tries to insert a character that'll turn the string invalid, the test will restore the last valid value.
Hence, the updated directive:
angular.module('app', [])
.directive('allowTyping', function() {
return {
restrict : 'A',
link : function(scope, elem, attrs, ctrl) {
var regex = attrs.allowTyping;
var lastInputValue = "";
elem.bind('keyup', function(event) {
var input = elem.val();
var validator = new RegExp(regex);
if (!validator.test(input))
// Restore last valid input
elem.val(lastInputValue).trigger('input');
else
// Update last valid input
lastInputValue = input;
});
}
};
});

How To Check For Empty Fields In HTML Form With JavaScript

I'm checking a website registration form with JavaScript code and onchange listeners.
Empty fields/spaces need to be checked for first before checking for illegal characters, too long strings, etc.
I've read this.
But for a null string,
if (field.value ==="")
alert("Empty field!");
this will not generate the desired alert.
People at the end of the above thread suggested that recent browser versions might not accept such a statement.
So, how do I sort out empty/blank/ignored fields ?
EDIT 1
I've already tried
if (!field.value)
but it only provides an alert if the user has already typed some characters in the field and immediately deleted them before entering a blank field. It will not provide an alert just by clicking the mouse on it and then tabbing on to the next field. It looks like I may need to assign a null value to these form fields at the outset . . I am using implicit adding of the changeEvent listener, i.e. on seeing a value explicitly assigned to the onchange attribute of an element, it is activated without any addEventListener(..) statement.
Also,
if (field.value.length == 0)
does not seem to produce any alert.
EDIT 2
Sorted, I think.
I was using the JavaScript null field check as part of a field-by-field validation check on a web form.
I was using onchange as the event handler. This was wrong. What was needed here was onblur since in the case of a completely null field (i.e. a field on which nothing had been entered before tabbing away from it), no change has been effected -- and therefore no onchange event occurs that would trigger a JavaScript alert.
Thanks for your efforts.
I was stuck on this one across a couple of weeks and only sorted it with the help of some experimental programming by a more experienced guy at work here.
In this script you can see an alert of your variable value ( a console.log would be lees noisy :)
The use of === is for type check but in your example does not make sense as you are using an empty string
<script>
var field= {};
checkEquality(field);
field.value = "";
checkEquality(field);
function checkEquality(object){
alert(object.value);
if (object.value === "")
{
alert("===");
}
if(object.value == ""){
alert("==");
}
}
You can use bellow code for check all four(4) condition for validation like not null, not blank, not undefined and not zero only use this code (!(!(variable))) in javascript and jquery.
function myFunction() {
var data; //The Values can be like as null, blank, undefined, zero you can test
if(!(!(data)))
{
alert("data "+data);
}
else
{
alert("data is "+data);
}
}

Building A Timer in Javascript/jQuery

I'm currently working on a website which would work somewhat like this one:
http://www.keyhero.com/free-typing-test/
I'd like to build a timer which starts on the user's first key press, and ends when the user presses 'Enter'. When the timer ends, I'd want to get the resulting time and push it off to the backend (Django for me) where the WPM will be calculated. However, I have no idea how to get started on this, as I have little experience with jQuery and Javascript and haven't been able to find useful pages.
So my question is, how would I do this? I can post some of my Django files if a better picture of what I am asking is needed.
You manage the key events with .keypress() for know when the person click in the keyboard.
http://api.jquery.com/keypress/
And for key = "ENTER", you just need to validate if is equal to key 13
$('#id_tag').keypress(function (e) {
var key = e.which;
if(key == 13) // the enter key code
{
alert("Clicked on enter");
return false;
}
});
I see that post too, that are using keyup():
JQuery Event for user pressing enter in a textbox?
Well you're going to have to capture the keypress/keydown/keyup event in a jQuery event, then start a setInterval and have that function increment a counter variable of sorts. Then you just reference that variable whenever you want to see how many seconds have passed. A crude example would be:
window.secondsSinceTyped = 0;
$('#textbox').one('keypress', function (e) {
window.setInterval(function () {
window.secondsSinceTyped++;
}, 1000);
});
Then just reference the variable secondsSinceTyped whenever you want to check the time. Note that I used the jquery one binding which will only bind the event once so that every time you type it doesn't rebind the event.

jQuery UI autocomplete - matched option not getting selected on focusOut

I have made a jQuery UI - autocomplete in which I have added the following conditions:
Sorted option list
mandatory select an option
Here I made a mistake that, when I type the exact option, it is not getting selected.
For example:
var json = ["JOAQUIN", "BERNARDINO", "MODOC", "ALASKA", "MADERA", "ANDERSON",
"Kingston", "Kala", "Aka"];
I type aka, it is not accepting.
I also tried Aka, still it is not accepting.
How to reproduce:
After typing the above text in textbox, just focusout - The value
will be emptied. I want to handle aka = Aka = AKA (with/without selecting from option list)
PS: I'm not asking for code, I have made a small blunder which I need it to be correct.
Here is my fiddle, where you can easily reproduce the exception.
How can I change my code to work like this. Please share your suggestions and point me in a right direction on placing the conditions in order.
What if you change the event to close instead of the change event. Then check if the value is in the array and if not clear the container. This will require you match capitalization when you enter the value. If you wanted to ignore capitalization you could do a convert the json array to lower case and the value to lowercase before search the array.
fiddle
var json = ["JOAQUIN", "BERNARDINO", "MODOC", "ALASKA", "MADERA", "ANDERSON", "Kingston", "Kala", "Aka"];
$('input').autocomplete({
source: function (request, response) {
var matches = $.map(json, function (json) {
if (json.toUpperCase().indexOf(request.term.toUpperCase()) === 0) {
return json;
}
});
response(matches);
},
close: function(event, ui) {
var index = jQuery.inArray($('input').val(), json);
if (index == -1) {
$('input').val("");
}
}
});
As far as I can see from the documentation the UI property only gets set when a dropdown item is selected (clicked on) so you will have to add to your validation in the change function to manually check against the values.

onkeyup() event - possible to detect if key press was ignored because a field was at it's maxlength?

I have a form field for entering a user id. The user id is always 6 characters so the field is limited to a maxlength of 6 characters.
The field has an onkeyup() event to call a function that looks up the user id and fills in several other form fields if the user id is valid. Most people I know have used onblur() for something like this but I never liked how a user has to tab to or click on another field before the autofilling AJAX goes off.
The function right now will return w/o doing anything if the field length is < 6 characters or if the key that was pressed is a left or right cursor field.
There's one flaw left I haven't been able to think around. Since the field is limited to 6 characters, if 6 characters are already entered and another key is pressed, the value of the field will not change but the function will fire and validate the field (un-necessary validation/db-query).
Is there anyway to prevent the function from going off in this case? I'm thinking it's not possible but wanted to check. I had a thought if the field length was 6 I could check the last key pressed against the 6th character of the field, but if someone typed something like 'a' as the 6th character and then 'a' again, it wouldn't work. I guess that could eliminate all possibilities except for the one case though one case (not perfect but better).
(rails) <%= f.text_field :uid, :size => 10, :maxlength => 6, :class => 'validate_text', :onkeyup => uid_lookup (event)', :autocomplete => :off %>
<script type="text/javascript">
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g,"");
}
</script>
<script type="text/javascript">
uid_lookup = function(e){
var unicode=e.keyCode? e.keyCode : e.charCode;
if (unicode == 37 || unicode == 39) { // ignore a left or right arrow press
return
}
var uid= $('uid').value;
uid = uid.trim();
$('uid).value = uid; //uid's have no spaces, go ahead and remove if typed
if (uid.length != 6) {
return
}
// db lookup & form autofill
}
</script>
The solution is not to use onkeyup. The correct event to use is HTML5's oninput, which is supported by almost every major browser out there. The one browser lacking support is—of course—Internet Explorer 8, but we can emulate the event using IE's proprietary onpropertychange event which will fire whenever an input element's value property changes.
I'm not familiar with rails, but the best way to apply the event is using JavaScript so that you can gracefully degrade if oninput isn't supported:
var el = document.getElementById("myEl");
// Check support
if ("onpropertychange" in el && !("oninput" in el)) {
el.onpropertychange = function () {
if (event.propertyName == "value")
uuid_lookup.call(this, event);
}
}
else
el.oninput = uuid_lookup;
The other great thing about this event is that it only fires when the value changes - much like onchange but more real-time. This means you can do away with your key detection for left and right arrows in the uuid_lookup function.
Can you not use another variable to store the previous value of the field the last time the validation function was fired? Then you can simply return if uid.value = lastValue.
Not perfect, but it would save some processing.

Categories

Resources