jQuery: Count words in real time - javascript

I am using the following jQuery functionality to count words in real time:
$("input[type='text']:not(:disabled)").each(function(){
var input = '#' + this.id;
word_count(input);
$(this).keyup(function(){
word_count(input);
})
});
var word_count = function(field) {
var number = 0;
var original_count = parseInt($('#finalcount').val());
var matches = $(field).val().match(/\b/g);
if(matches) {
number = matches.length/2;
}
$('#finalcount').val(original_count + number)
}
The issue I am running into is that when I start typing in an input field, the count increases immediately by two, even on spaces and my delete key. Any ideas why this would happen?
I was following this tutorial: http://www.electrictoolbox.com/jquery-count-words-textarea-input/
Input:
<input class="widest" id="page_browser_title" name="page[browser_title]" size="30" type="text" value="">
Display Input:
<input class="widest" disabled="disabled" id="finalcount" name="page[word_count]" size="30" type="text" value="662">

It is incrementing with every key press because you are telling it to with:
$('#finalcount').val(original_count + number)
And if you add another word, you will find that it increments not by 2, but by 3. Presumably, you have several inputs on the page, and you intend for the finalcount input to display the number of words in each input. Either store the counts in a variable and add the variables together to get your finalcount value. Or count the words in each input every time.
var wordCounts = {};
function word_count (field) {
var number = 0;
var matches = $(field).val().match(/\b/g);
if (matches) {
number = matches.length / 2;
}
wordCounts[field] = number;
var finalCount = 0;
$.each(wordCounts, function(k, v) {
finalCount += v;
});
$('#finalcount').val(finalCount)
}
Working demo: http://jsfiddle.net/gilly3/YJVPZ/
Edit: By the way, you've got some opportunities to simplify your code a bit by removing some redundancy. You can replace all of the JavaScript you posted with this:
var wordCounts = {};
$("input[type='text']:not(:disabled)").keyup(function() {
var matches = this.value.match(/\b/g);
wordCounts[this.id] = matches ? matches.length / 2 : 0;
var finalCount = 0;
$.each(wordCounts, function(k, v) {
finalCount += v;
});
$('#finalcount').val(finalCount)
}).keyup();
http://jsfiddle.net/gilly3/YJVPZ/1/

Edit
Check this example.
Why don't you use split(" ") instead of matching and dividing the result? You will have an array containing all your words, the length of the array will be the number of words.
var matches = $(field).val().split(" ");
Also, why are you adding every time the matches to the old result?
$('#finalcount').val(original_count + number)
Isn't this adding every time all the words twice?

Related

How to split String, convert to Numbers and Sum

I have a function that I have modified to get a string (which consists of zeros and ones only).
The string (timesheetcoldata):
100000000000000000000000100000000000000000000000100000000000000000000000100000000000000000000000100000000000000000000000100000000000000000000000
The string items (the numbers one and zero) will change every time the function is run.
It will always be the same length.
I have made the string above easier to see what I am trying to achieve.
I want to return the first character and then every 24th character (as in the variable colsCount in the function).
so, in the example above, it would return something like: 111111
I then want to convert these characters to numbers (something like [1, 1, 1, 1, 1, 1]).
I then want to sum these number together (so it would return, in the example: 6).
I then want to check if the returned number matches the variable: rowsCount
or true if it does, false if it does not.
My function:
$("#J_timingSubmit").click(function(ev){
var sheetStates = sheet.getSheetStates();
var rowsCount = 6;
var colsCount = 24;
var timesheetrowsdata = "";
var timesheetcoldata = "";
for(var row= 0, rowStates=[]; row<rowsCount; ++row){
rowStates = sheetStates[row];
timesheetrowsdata += rowStates+(row==rowsCount-1?'':',');
}
timesheetcoldata = timesheetrowsdata.replace(/,/g, '');
console.log(timesheetcoldata);
});
Thank you very much to both Rajesh and MauriceNino (and all other contributers).
With their code I was able to come up with the following working function:
$("#J_timingSubmit").click(function(ev){
var sheetStates = sheet.getSheetStates();
var rowsCount = 6;
var timesheetrowsdata = "";
var timesheetcoldata = "";
for(var row= 0, rowStates=[]; row<rowsCount; ++row){
rowStates = sheetStates[row];
timesheetrowsdata += rowStates+(row==rowsCount-1?'':',');
}
timesheetcoldata = timesheetrowsdata.replace(/,/g, '');
var count = 0;
var list = [];
for(var i = 0; i< timesheetcoldata.length; i+=24) {
const num1 = Number(timesheetcoldata.charAt(i));
list.push(num1);
count += num1;
}
let isSameAsRowsCount = count == rowsCount;
console.log('Is Same? ', isSameAsRowsCount);
});
You can always rely on traditional for for such action. Using functional operations can be more readable but will be more time consuming(though not by much).
You can try this simple algo:
Create a list that will hold all numbers and a count variable to hold sum.
Loop over string. As string is fixed, you can set the increment factor to the count(24).
Convert the character at given index and save it in a variable.
Push this variable in list and also compute sum at every interval.
At the end of this loop, you have both values.
var string = '100000000000000000000000100000000000000000000000100000000000000000000000100000000000000000000000100000000000000000000000100000000000000000000000';
var count = 0;
var list = [];
for(var i = 0; i< string.length; i+=24) {
const num1 = Number(string.charAt(i));
list.push(num1);
count += num1;
}
console.log(list, count)
Here is a step by step explanation, on what to do.
Use match() to get every nth char
Use map() to convert your array elements
Use reduce() to sum your array elements
Everything needed to say is included in code comments:
const testData = '100000000000000000000000100000000000000000000000100000000000000000000000100000000000000000000000100000000000000000000000100000000000000000000000';
// Step 1) Create array of numbers from string
const dataArr = testData.match(/.{1,24}/g) // Split on every 24th char
.map(s => Number(s[0])) // Only take the first char as a Number
console.log(dataArr);
// Step 2) Sum array Numbers
let dataSum = dataArr.reduce((a, b) => a + b); // Add up all numbers
console.log(dataSum);
// Step 3) Compare your variables
let rowsCount = 123; // Your Test variable
let isSameAsRowsCount = dataSum == rowsCount;
console.log('Is Same? ', isSameAsRowsCount);
As #Jaromanda mentioned, you can use the following to done this.
const string = '100000000000000000000000100000000000000000000000100000000000000000000000100000000000000000000000100000000000000000000000100000000000000000000000';
const value = string.split('').filter((e,i)=> !(i%24)).reduce((acc,cur)=> acc+ (+cur), 0);
console.log(value);

How to modify data-list element or alternative

I am working on an autocomplete text input by testing for string similarity, rather than checking for perfect character matches. This way, a dropdown like a datalists would still present the user with suggestions even if they accidentally add an extra character or spell their desired input wrong.
I have a working Javascript file that can compare the string input from an HTML text input to all the strings in a JSON file that holds about 700 school names as strings. The Javascript file then formats the HTML and passes the 10 most similar strings into an unordered list(for debugging) and into a data-list (where the user will be able to pick their correct answer).
However, datalists seem to have built-in autocomplete that check for identical groups of characters and the datalists will intelligently remove suggestions if the inputted string does not exist within the suggestion.
<input
type ="text"
id="search"
list="hsDropdown"
class ="form-control form-control-lg"
placeholder="High School Name"
autocomplete="off"
autofocus = "false"
/>
<hr/>
<p id="word"></p>
<datalist id ="hsDropdown"></datalist>
<ul id ="list"></ul>
</main>
<script src="js/script.js" type ="text/javascript"></script>
<script src="js/ukkonen/index.js" type ="text/javascript"></script>
The options within the datalist in my HTML are properly populated by my script.js with the most similar strings, but I need to find a way to override the property of the datalist tag that causes results with nonperfect matches to not appear, or
I would need to find an alternative way to make a dropdown list appear from a textbox that is not limited to hard auto-correct.
You could look at the select2 jQuery plugin and the Fuzzy search issue opened there
As per requestor, he has implemented the fuzzy_match function and embedded it into the plugin as the following:
I've also a function called matcher, which looks something like:
function matcher(term, text){
if(term.term === undefined){
return {text: text, score: 1};
}
var match = fuzzy_match(term.term, text.text);
return (match[0])?{text: text, score: match[1]}:false;
}
I also have a sorter, which sorts the matched elements, (so matching elements come at top)
function sorter(data) {
return data.filter(function(item) {
return !!item;
}).sort((a, b) => b.score - a.score)
.map(item => item.text);
}
And whenever we're invoking a select2 on a element, we're passing this matcher as a matcher option, and sorter as sorter option, which looks something like:
$("#element").select2({
placeholder: 'select a name',
matcher,
sorter
})
Here is the fuzzy_match function code provided:
/**
*
* #param pattern
* #param str
* #returns {[boolean,score,formatted]}
*/
function fuzzy_match(pattern, str) {
// Score consts
var adjacency_bonus = 55; // bonus for adjacent matches
var separator_bonus = 10; // bonus if match occurs after a separator
var camel_bonus = 10; // bonus if match is uppercase and prev is lower
var leading_letter_penalty = -3; // penalty applied for every letter in str before the first match
var max_leading_letter_penalty = -9; // maximum penalty for leading letters
var unmatched_letter_penalty = -1; // penalty for every letter that doesn't matter
// Loop variables
var score = 0;
var patternIdx = 0;
var patternLength = pattern.length;
var strIdx = 0;
var strLength = str.length;
var prevMatched = false;
var prevLower = false;
var prevSeparator = true; // true so if first letter match gets separator bonus
// Use "best" matched letter if multiple string letters match the pattern
var bestLetter = null;
var bestLower = null;
var bestLetterIdx = null;
var bestLetterScore = 0;
var matchedIndices = [];
// Loop over strings
while (strIdx != strLength) {
var patternChar = patternIdx != patternLength ? pattern.charAt(patternIdx) : null;
var strChar = str.charAt(strIdx);
var patternLower = patternChar != null ? patternChar.toLowerCase() : null;
var strLower = strChar.toLowerCase();
var strUpper = strChar.toUpperCase();
var nextMatch = patternChar && patternLower == strLower;
var rematch = bestLetter && bestLower == strLower;
var advanced = nextMatch && bestLetter;
var patternRepeat = bestLetter && patternChar && bestLower == patternLower;
if (advanced || patternRepeat) {
score += bestLetterScore;
matchedIndices.push(bestLetterIdx);
bestLetter = null;
bestLower = null;
bestLetterIdx = null;
bestLetterScore = 0;
}
if (nextMatch || rematch) {
var newScore = 0;
// Apply penalty for each letter before the first pattern match
// Note: std::max because penalties are negative values. So max is smallest penalty.
if (patternIdx == 0) {
var penalty = Math.max(strIdx * leading_letter_penalty, max_leading_letter_penalty);
score += penalty;
}
// Apply bonus for consecutive bonuses
if (prevMatched)
newScore += adjacency_bonus;
// Apply bonus for matches after a separator
if (prevSeparator)
newScore += separator_bonus;
// Apply bonus across camel case boundaries. Includes "clever" isLetter check.
if (prevLower && strChar == strUpper && strLower != strUpper)
newScore += camel_bonus;
// Update patter index IFF the next pattern letter was matched
if (nextMatch)
++patternIdx;
// Update best letter in str which may be for a "next" letter or a "rematch"
if (newScore >= bestLetterScore) {
// Apply penalty for now skipped letter
if (bestLetter != null)
score += unmatched_letter_penalty;
bestLetter = strChar;
bestLower = bestLetter.toLowerCase();
bestLetterIdx = strIdx;
bestLetterScore = newScore;
}
prevMatched = true;
}
else {
// Append unmatch characters
formattedStr += strChar;
score += unmatched_letter_penalty;
prevMatched = false;
}
// Includes "clever" isLetter check.
prevLower = strChar == strLower && strLower != strUpper;
prevSeparator = strChar == '_' || strChar == ' ';
++strIdx;
}
// Apply score for last match
if (bestLetter) {
score += bestLetterScore;
matchedIndices.push(bestLetterIdx);
}
// Finish out formatted string after last pattern matched
// Build formated string based on matched letters
var formattedStr = "";
var lastIdx = 0;
for (var i = 0; i < matchedIndices.length; ++i) {
var idx = matchedIndices[i];
formattedStr += str.substr(lastIdx, idx - lastIdx) + "<b>" + str.charAt(idx) + "</b>";
lastIdx = idx + 1;
}
formattedStr += str.substr(lastIdx, str.length - lastIdx);
var matched = patternIdx == patternLength;
return [matched, score, formattedStr];
}

JS how to Sum/Average?

I have an object and need to sum/average of each dynamic span. Can't seem to convert those to numbers though. Please Help.
Console Log
Code Sample
Expand/Collapse Created : 1/3/2017 ‎<span>(10)‎</span>
Expand/Collapse Created : 1/4/2017 ‎<span>(38)‎</span>
Expand/Collapse Created : 1/5/2017 ‎‎<span>(13)</span>
Expand/Collapse Created : 1/6/2017 ‎‎<span>(35)</span>
Expand/Collapse Created : 1/9/2017 ‎‎<span>(46)</span>
Expand/Collapse Created : 1/10/2017 ‎‎<span>(17)</span>
Expand/Collapse Created : 1/11/2017 ‎‎<span>(27)</span>
var arr = [];
$(".ms-gb span").each(function(index, elem){
arr.push($(this).text());
});
$("#result").text(arr.join("+")); // (10)+‎(38)+‎(13)+‎(35)+‎(46)+‎(17)+‎(27)
var allNumbers = document.getElementById('result').innerText; // (10)+‎(38)+‎(13)+‎(35)+‎(46)+‎(17)+‎(27)
allNumbers = allNumbers.replace(/["'()]/g,""); // ‎‎10+‎38+‎13+‎35+‎46+‎17+‎28
var newString = allNumbers.split("+"); // Object - ["‎10", "‎38", "‎13", "‎35", "‎46", "‎17", "‎27"]
well you're pretty close. i'd recommend using the reduce function
var sum = allNumbers.reduce(function(a,b){ return +a + +b; }, 0)
the plus signs in front of a and b might look weird, but its a quick way to coerce a string into a number in javascript
You can strip out non-numeric characters, parse each number, and then perform the addition within the loop.
// variables
var sum = 0;
var average = 0;
var numOfSpan = $('span').length;
// each span loop
$('span').each(function(key, val){
// add the value of the span to the sum var
sum+= parseInt($(this).text().replace(/\D/g,''));
// on the last itteration ...
if(key == (numOfSpan - 1)) {
// calulate average
average = sum / numOfSpan;
// log sum and average
console.log('sum = ' + sum);
console.log('average = ' + average);
}
});
<span>(10)</span>
‎<span>(38)</span>
<span>(13)</span>
<span>(35)</span>
‎<span>(46)</span>
‎‎<span>(17)</span>
<span>(27)</span>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You have to iterate your array and then change every string to number. After that you can add every elements to itself.
var a = 0;
for(var i=0;i<newString.length;i++) {
a += parseInt(newString[i]);}
And then a will be the sum

How can I get the result to return '0' instead of NaN if nothing is entered into the field.

If the field is left blank, it will return NaN as the average. How can I get this to return 0 instead?
This is what I have for my HTML file:
<html>
<head>
<title> Average Numbers </title>
<script type="text/javascript" src="arrays.js"></script>
<script type="text/javascript">
function ShowAvg()
// Assumes: numsBox contains a sequence of numbers
// Results: displays the average of the numbers in outputDiv
{
var str, strArray, numArray;
str = document.getElementById('numsBox').value;
if ( isNan(str)){
document.getElementById('numArray').value = '0';
}
strArray = str.split(/[, \t\n]+/); // SPLIT STRING INTO AN ARRAY
numArray = ParseArray(strArray); // STORE ARRAY VALUES AS NUMS4
document.getElementById('outputDiv').innerHTML =
'The average of [' + numArray + '] is ' + Average(numArray);
}
</script>
</head>
<body>
<p>
Enter numbers: <input type="text" id="numsBox" size=40 value="">
</p>
<p>
<input type="button" value="Compute the Average" onclick="ShowAvg();">
</p>
<div id="outputDiv"></div>
</body>
</html>
This is my javascript file:
function Acronym(phrase)
// Assumes: phrase is a string of words, separated by whitespace
// Returns: the acronym made up of first letters from the words
{
var words, acronym, index, nextWord;
words = phrase.split(/[ \t\n]+/); // CONVERT phrase TO AN ARRAY
acronym = ''; // INITIALIZE THE acronym
index = 0; // START AT FIRST WORD
while (index < words.length) { // AS LONG AS WORDS LEFT
nextWord = words[index]; // GET NEXT WORD
acronym = acronym + nextWord.charAt(0); // ADD FIRST CHAR OF WORD
index = index + 1; // GO ON TO NEXT WORD
}
return acronym.toUpperCase(); // RETURN UPPER CASE acronym
}
function ParseArray(strArray)
// Assumes: strArray is an array of strings representing numbers
// Returns: a copy of strArray with items converted to numbers
{
var numArray, index;
numArray = [ ]; // CREATE EMPTY ARRAY TO STORE COPY
index = 0; // FOR EACH ITEM IN strArray
while (index < strArray.length) { // CONVERT TO NUMBER AND COPY
numArray[index] = parseFloat(strArray[index]);
index = index + 1;
}
return numArray; // FINALLY, RETURN THE COPY
}
function Average(numArray)
// Assumes: numArray is an array of numbers
// Returns: average of the numbers in numArray
{
var sum, index;
sum = 0; // INITIALIZE sum
index = 0; // START AT FIRST NUMBER
while (index < numArray.length) { // AS LONG AS NUMBERS LEFT
sum = sum + numArray[index]; // ADD NUMBER TO sum
index = index + 1; // GO ON TO NEXT NUMBER
}
return sum/numArray.length; // RETURN AVERAGE
}
Thanks in advance for any and all help. I'm a noob at this and have been struggling for hours trying to figure this out.
to make a long story short, just add
value = value || 0;
for a default value.
I got following problems
isNan() should be isNaN()
document.getElementById('numArray').value = '0'; is not working, because its a button, not an input field, Use instead document.getElementById('numsBox').value = '0';.
Longer answer: In your js file, replace
return numArray;
}
with
avgNum = sum/numArray.length;
if (avgNum != avgNum) {
avgNum = 0;
}
return avgNum; // RETURN AVERAGE
}
You will still need to change the str NaN display in the html file (I would just take it out and say the average is: since the numbers are still shown at the top). The reason this works is that the only this NaN is not equal to is itself (which is sooo weird). Code On!

jQuery: Count words with more than X characters in string

I have an input field and a textarea in a form. For each of those two fields I'd like to count the number of words, number of dots, and number of words longer than 7 characters.
I've already got the code for the two first numbers (number of words and number of dots), but I can't figure out how to count the number of words longer than 7 characters in each of the fields.
Can anyone help me out with this one?
Here is my current code (fiddle):
var titleElem = $('#title');
var numberOfWords = countWords(titleElem);
var numberOfDots = countDots(titleElem);
function countWords(input) {
var a, z, inputValue, total;
inputValue = input.val();
total = 0;
a = inputValue.replace(/\s/g, ' ');
a = a.split(' ');
for (z = 0; z < a.length; z++) { if (a[z].length > 0) total++; }
return total;
}
function countDots(input) {
var inputVal;
inputVal = input.val();
return inputVal.split(".").length - 1;
}
It depends what you classify as a word. Does that include hyphens/apostrophes? You could use a simple regex for this:
var wordCount = $("input").val().match(/[\w0-9]{8,}/gi).length
The {8,} ensures that it only captures words more than 7 characters in length.

Categories