Delaying regex for more specific matches - javascript

I have a scenario where I need to run regex tests on a numeric input that represents phone company operator services.
In one instance, there is a prefix 118 which can act on its own or with a suffix of up to two digits.
At the moment, my function looks something like the below. My problem is that the least specific '118' exact match fires before the more specific one.
There is no sleep/wait in Javascript and unless I'm mistaken, I don't think I can get setTimeout to return a simple "return true" ?
I don't mind if the answer to this question is in pure Javascript or Jquery, but not having a dependency on Jquery would be preferable.
function isOperatorService(vNumber) {
var vNumber = vNumber.replace(/\D/g,'');
if (/^((44){0,1}118[0-9]{3})$/.test(vNumber)) {
console.log("118 specific");
return true;
}
if(/^((44){0,1}[19]{1}[0-9]{1}[0-79]{1})$/.test(vNumber)) {
console.log("Other shortcodes");
return true;
}
return false;
}
UPDATE: Re: "Provide your input and expected output."
Pretty much as I described above, in Pseudo-code :
if == 118
wait incase the user has not finished typing (e.g. wait incase 118118...)
else
do other regex cheks

Add a simple debouncer:
var timeout;
var typeDelay = 300; // wait after last type
var changeEvents = "propertychange keyup input paste change";
$('#yourinput').on(changeEvents, function () {
//clear your timeout
clearTimeout(timeout);
// Add another listener
timeout = setTimeout(function () {
// Do your regex here
function_ended_typing();
}, typeDelay);
});

Related

How to accept all punctuation from form input

I'm creating a language quiz where users can write down their answers, like a translation for example. But I've noticed, when the answer requires punctuation, like a quotation mark, that some devices use a different style of punctuation and that will result in a wrong answer, because the punctuation used in the correct answer is just a bit different.
Here's the javascript I'm using to check answers:
<script>
var answers = {
q1: ["Auto's"]
};
function markAnswers(id) {
$(`#q${id}`).each(function () {
if ($.inArray(this.value, answers[this.id]) === -1) {
$(this).parent().append(`<br><span class='incorrect'>✗ Correct answer = ${answers[this.id]}</span>`);
} else {
$(this).parent().append("<br><span class='correct'>✓ Correct!</span>");
}
});
}
$("form").on("submit", function (e) {
e.preventDefault();
const id = e.target.id.replace("formId", "");
markAnswers(id);
$(`#submitId${id}`).each(function () {
this.setAttribute("disabled", true);
this.value = "Check answer";
});
});
</script>
As you can see, here I have an answer that requires a single quote ('), but apparently not all single quotes are equal.
I did find some code to replace specific punctuation and it's this: [^\w\s\']|_
But I'm not sure how to implement it and I would rather just accept the different punctuation. My only concerns are quotation marks and whitespace (as autofill on phones and tablets can create space).
Any suggestion on how to implement this is much appreciated. Thanks!
Edit:
Based on #Don't Panic's earlier versions of his code below (without .clean and .display) I want to make a few tweaks to it, but before I can, the code below always shows ✗ Correct answer = no matter if the answer is typed correctly or not. What could be wrong?
And about the tweaks. I've come to understand that Apple uses Smart Punctuation and this is a feature you can turn off. So I will ask my students to do that. Because I've tested it and without this feature toggled on, it will display a more straight/normal apostrophe and the answer will be accepted as correct. But since apostrophes and perhaps some other punctuation like a comma will be important, I want to add those to the existing line of code ^a-zA-Z\d. And I was thinking to at least ignore periods and extra spaces.
Thank you for all the help!
// Write out your answers without punctuation
var answers = {
q1: ["Autos"]
};
function markAnswers(id) {
$(`#q${id}`).each(function () {
// First, strip out any punctuation the user has entered
let userAnswer = this.value.replace(/[^a-zA-Z\d]/g,'');
// Now check if that "cleaned" value matches your answer
if ($.inArray(userAnswer, answers[this.id]) === -1) {
$(this).parent().append(`<br><span class='incorrect'>✗ Correct answer = ${answers[this.id]}</span>`);
} else {
$(this).parent().append("<br><span class='correct'>✓ Correct!</span>");
}
});
}
Take the user's input, strip out all punctuation, do the same to your answer, and compare the two. This way a user can use any punctuation they like, but it will just be stripped out and not part of the comparison.
This won't work if your questions are eg related to grammar, eg if you are testing when and where an apostrophe is correct, of course - in those cases the apostrophe is the answer!
The exact regular expression will depend on what has to be in your questions. Let's say you need all uppper- and lower-case letters and numbers:
$('button').on('click', function() {
markAnswers(1)
});
// Correct answers
var answers = {
"q1": "Auto's"
};
function markAnswers(id) {
$(`#q${id}`).each(function () {
// First, strip out any punctuation the user has entered
let userAnswer = this.value.replace(/[^a-zA-Z\d]/g,'');
// Strip any punctuation from the right answer
let correct = answers[this.id].replace(/[^a-zA-Z\d]/g,'');
// Now check if they match
if (userAnswer !== correct) {
$(this).parent().append(`<br><span class='incorrect'>✗ Correct answer = ${answers[this.id]}</span>`);
} else {
$(this).parent().append("<br><span class='correct'>✓ Correct!</span>");
}
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
What is the plural of <b>Auto</b>?
<input id='q1' type='text'>
<button>Check!</button>
</div>
To answer your new question - your code works fine for me.
I've added a button handler to actually run the check, but otherwise this is a copy-paste of your code.
If I type asdf, I get "✗ Correct answer = Autos", which is correct;
If I type autos, I get "✗ Correct answer = Autos", which is correct (lower case "a" instead of "A");
If I type Autos, I get "✓ Correct!", which is correct;
If I type "Auto's" (including those quotes), I get "✓ Correct!", which is correct;
$('button').on('click', function() {
markAnswers(1)
});
// Write out your answers without punctuation
var answers = {
q1: ["Autos"]
};
function markAnswers(id) {
$(`#q${id}`).each(function () {
// First, strip out any punctuation the user has entered
let userAnswer = this.value.replace(/[^a-zA-Z\d]/g,'');
// Now check if that "cleaned" value matches your answer
if ($.inArray(userAnswer, answers[this.id]) === -1) {
$(this).parent().append(`<br><span class='incorrect'>✗ Correct answer = ${answers[this.id]}</span>`);
} else {
$(this).parent().append("<br><span class='correct'>✓ Correct!</span>");
}
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
What is the plural of <b>Auto</b>?
<input id='q1' type='text'>
<button>Check!</button>
</div>

Dynamically Set ng-pattern base on user inputs

I have input box
<input type="text" class="text-primary" ng-pattern="ip_regex or ipv6_regex" name="newIP" ng-model="macbinding.newIP" ng-change="checkDuplicates('newIP')">
I already 2 patterns for IPv4 and IPv6 ready.
$scope.ip_regex = '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$';
$scope.ipv6_regex = '((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?';
But how can I apply those ng-pattern dynamically to that input base on-change if a string contain a : ?
Ex. 2001::1
If inputs contain : then, I know it an IPv6 then, I will use the ng-pattern="ipv6_regex"
Is this something that I can achieve on front-end HTML, or do I need to parse the input and do a logic in my angular controller ?
Can I use ng-if for that ?
You can use a combination of ng-model to store and examine your user's input, and a timeOut function to tell you when to check the input. For example.
Your input tag would look like this
<input id="textBox" ng-model="$ctrl.userInput" value="$ctrl.userInput"/>
And the Js (I wrote it in typescript, but it should be readable enough that you get the gist.)
userInput: string = '';
//setup before functions
typingTimer: number; //timer identifier
//on keyup, start the countdown
$('#textBox').on('keyup', function () {
if (typingTimer){
clearTimeout(typingTimer);
}
//if no keyup event is found in 3000ms (3 seconds) execute doneTyping()
typingTimer = setTimeout(doneTyping, 3000);
});
//user is "finished typing," do something
function doneTyping() {
//check for ':'
var foundSemiColon: boolean = false;
//for every character in, userInput see if that character's code value equals 58.
//58 is the ASCII representation of a semi-colon
for (var i: number = 0; i < userInput.length; i++) {
if (userInput.charCodeAt(i) === 58) {
//Semi-colon found use IPv6
break;
}
}
//if foundSemiColon === false you didn't find a semi-colon
if (!foundSemiColon) {
//use IPv4
}
//Now that you're done and know what to use, clear your timeOut event
clearTimeout(typingTimer);
}

Browser Crashes before Input is Entered

I'm writing a program in javascript that is supposed to be a fast paced typing challenge. The issue is that my script that's checking for input is crashing my browser before I can enter anything. I thought that it'd pause to wait for input but it seems like I might be wrong?
Here is the function that crashes my browser:
var level1 = function () {
var letter;
var ascii;
var ncorrect = 0;
var input = "0";
var timedout = false;
document.getElementById('prompt').text = "Level 1 : Type Here!" // this is supposed to change text on the page... It doesn't work but not that's not my question.
while (ncorrect < 26){
timedout = false;
setTimeout(timedout = true, 5000);
ascii = Math.floor(Math.random() * 122) + 97; // ASCII's of lower case letters
letter = String.fromCharCode(ascii);
document.getElementById('letter').text = letter;
input = document.getElementById('keyinput');
console.log(input);
if(!timedout && input === letter) {
clearTimeout();
ncorrect++;
}
else {
ncorrect = 0;
}
}
return 0;
}
If it's not a simple fix...
What would be a better way of monitoring input and responding to a right answer?
Thanks, I know it's a little broad of a question but I'm struggling to figure out what I'm looking for.
Javascript is already running an event loop in the background, so you don't need your own. This loop runs continuously and checks to see if any events have fired on any of the HTML DOM Elements. For example, if a button has been clicked, the event loop will pick up a click event for that element. You can add event handlers to the element, which are functions that fire when certain events occur to that element. What you want to do is set an event handler for the event that fires whenever the text in your input area (I'm assuming that the user is typing in an input or textarea tag) is fired.
For example, the following simple program will create a typing challenge 100 random characters long
var ncorrect = 0;
var timedout = false;
//select an empty paragraph to fill with text
var challengeText = document.getElementbyId("challengeText");
challengeText.innerHtml = "";
//Append 100 random characters to the paragraph
for (var i=0;i<100;i++) {
challengetText.innerHtml += String.fromCharCode(Math.floor(Math.random() * 122) + 97);
}
//Check the number of characters typed since the last the loop hit the element
var lastCharsTyped = 0;
var charsTyped = 0;
//Grab the user input
var input = document.getElementById("userInput")
//Set the event handler to fire when the user presses a key (technically, when they lift their finger
input.onkeyup = function(keyEvent){
//Ugly ternary to deal with the fact that not all browsers use the same api. If you haven't seen this before it means if which is a key of keyEvent then keyCoe is event.which, otherwise it's event.keyCode
var keyCode = ('which' in keyEvent) ? keyEvent.which : keyEvent.keyCode;
//Check if the key pressed is equal to the character in the text to match at the same position
if (keyCode === challengeText.innerHtml.charCodeAt(input.value.length)) { ncorrect ++} else {ncorrect = 0;}
}
It won't handle deletes or shift very gracefully, but it should give you an idea of the direction to take.
As a stylistic note, its customary to declare and initialize your variables right before you use them, rather than at the start of your program.
You can use setTimeout() and pass in a function that checks the input after whichever time you specify. Here's one way to implement this:
setTimeout( function () {
var textbox = document.getElementById('textbox');
if (textbox.value !== 'The quick brown fox jumps over the lazy dog.') {
alert('You didn\'t pass.');
} else {
alert('Congratulations!');
}
}, 5000);
Type in the phrase "The quick brown fox jumps over the lazy dog."
<input type="textbox" id="textbox"></input>
setTimeout is passed a function expression that checks user input and spits out an alert based on their typing prowess. The second argument 5000 means the function passed into setTimeout will be called at the nearest opportunity after 5000 ms has passed.

Limit html text input to a particular number of bytes?

Using HTML5 (or less preferably JavaScript) is it possible to limit the maximum length of an input to a particular number of bytes?
I realise that I can limit to a number of characters with:
<input type="text" maxlength="4" />
But that's not good enough because I can input up to four two-byte chars in it.
Obviously I am validating this server-side, but I would like this on the browser-side too.
Edit: Just to be clear, I do wish to be able to support UTF-8. Sorry #elclanrs.
this script has a couple minor UX glitches that can be cleaned up, but it does accomplish the basic task outlined when i tested it in chrome:
<input id=myinp />
<script> // bind handlers to input:
myinp.onkeypress=myinp.onblur=myinp.onpaste= function vld(e){
var inp=e.target;
// count bytes used in text:
if( encodeURIComponent(inp.value).replace(/%[A-F\d]{2,6}/g, 'U').length > 4){
// if too many bytes, try to reject:
e.preventDefault;
inp.value=inp.val||inp.value;
return false;
}
// backup last known good value:
inp.val=inp.value;
}
</script>
Throughout my own findings, I figured this works really well:
function limit_input(n) { // n = number of bytes
return function(e) {
const is_clipboard = e instanceof ClipboardEvent;
if(is_clipboard && e.type != "paste") {
return;
}
let new_val = e.target.value;
if(is_clipboard) {
new_val += e.clipboardData.getData("text");
} else {
new_val += e.key;
}
if(new TextEncoder().encode(new_val).byteLength -
e.target.selectionEnd + e.target.selectionStart > n) {
if(e.target.value == "" && is_clipboard) {
const old = e.target.placeholder;
e.target.placeholder = "Text too long to paste!";
setTimeout(function() {
e.target.placeholder = old;
}, 1000);
}
e.preventDefault();
}
};
}
let el = document.getElementById("your_input");
el.onkeypress = el.onpaste = limit_input(4);
I started out with dandavis' answer and kept on improving it to adapt to all situations. I still don't think this is perfect, and it's still using the deprecated onkeypress handler, but nothing else worked better than this.
You can delete the part of the code that changes placeholder to say the text is too long to paste (delete the whole if, keep only e.preventDefault() in). It's just something I added myself to notify the user why the input is still empty after they try pasting something in. That way they won't blame me for writing faulty code and I won't have to answer a horde of complaints.
If estimating isn't good enough, I'd filter all the non single-byte chars and count them.

Find the first character of input in a textbox

I am stuck in implementing the following:
User starts typing in a textbox.
The javascript on page captures the first character typed, validates that it is an english alphabet (a-z,A-Z) and converts it to lowercase (if necessary).
Make an XMLHttp request based on the input (i.e. if first input character is a, get a.xml, if b get b.xml and so on).
I know how to do the last part (make the xmlhttp request) but am kind of stuck on how to capture the first character and validate it (in a way that works on all browsers). Please guide. Thanks.
Clarification: This is to create a Google Suggest like autocomplete-drop-down menu without the need for server side programs.
Something like this should work:
HTML:
<input type="text" id="myField" />
And in JS:
window.onload = function() {
document.getElementById('myField').onkeyup = function() {
// Validate that the first letter is A-Za-z and capture it
var letter = this.value.match(/^([A-Za-z])/);
// If a letter was found
if(letter !== null) {
// Change it to lowercase and update the value
letter = letter[0].toLowerCase();
this.value = letter + this.value.substring(1);
// Do the request
}
}
}
My vanilla-JS skills are a bit rusty but this should do the trick. Just for the heck of it, here's the same using jQuery:
$(function() {
$('#myField').keyup(function() {
var letter = $(this).val().match(/^([A-Za-z])/);
// If a letter was found
if(letter !== null) {
// Change it to lowercase and update the value
letter = letter[0].toLowerCase();
$(this).val(letter + $(this).val().substring(1);
// Do the request
}
});
});
What part of the problem do you not know how to do? Here's an approach that you can follow. Very likely to need adjustments, but a good starting point
if our text field's id is 'txt'
document.getElementByID('txt').onkeypress = function(e) {
var textInField = this.value;
if (textInField.length == 1) {
var firstChar = textInField.charAt(0);
if (/[a-zA-Z]/.test(firstChar)) {
sendXHR(textInField.value.toLowerCase())
}
} else {
// What do you do if there is one or more chars???
}
}
Note that the other answers here mention onchange, that doesn't fire until the focus leaves the field, which I don't think is what you want

Categories

Resources