I've made a counter with javascript that shows a user how characters are remaining (from a set limit) for some text input or text area. Here's the code:
<script type="text/javascript">
function CountRemaining()
{
var limit = 1000;
var count = document.getElementById('press-form-body').value.length;
document.getElementById('counter').innerHTML = ((limit-count) + " characters left");
var timer = setTimeout("CountRemaining()",50);
}
</script>
My abomination above works fine but my problem is that I need to use this multiple times and making a separate function for every time I need it would be impractical to say the least.
I tried this and it didn't work:
<script type="text/javascript">
function CountRemaining(string, targetcounter, limit)
{
var count = document.getElementById(string).value.length;
document.getElementById(targetcounter).innerHTML = ((limit-count) + " characters left");
var timer = setTimeout("CountRemaining()",50);
}
I then figured I put the wrong statement for the timer so I changed it to this but still didn't work:
var timer = setTimeout("CountRemaining(string, targetcounter, limit)",50);
I'm lost. Any help would be highly appreciated. Thank you!
I think a better idea would be to use the "onchange" event for those types of elements.
Basically as soon as the text area / text input loses focus and is changed, you can bind a function to count how many characters are left.
document.getElementById('press-form-body').onchange = function() {
// your stuff (double check this to make sure the "this" value is right
// use this as an example
document.getElementById(targetcounter).innerHTML = this.value.length - 1000
}
Another solution would be to use the "key" events to listen to any keypress in the inputs.
document.getElementById('press-form-body').onkeypress = function() {
// your stuff (double check this to make sure the "this" value is right
// use this as an example
document.getElementById(targetcounter).innerHTML = this.value.length - 1000
}
function limittxt()
{
var tval = document.getElementById('press-form-body').value;
tlength = tval.length;
set = 100;
remain = parseInt(set - tlength);
document.getElementById('counter').innerHTML = remain + " characters left";
if (remain <= 0) {
document.getElementById('press-form-body').value = tval.substring(0, tlength - Math.abs(remain)))
}
}
An call this function in the input element like the following :
<input type='text' onkeypress='limittxt()' onkeyup='limittxt()' onkeydown='limittxt()'>
I suppose the error is with following line:
var timer = setTimeout("CountRemaining(string, targetcounter, limit)",50);
Here i think it should come like:
var str = "CountRemaining(" + string + "," + targetcounter + "," + limit + ")";
var timer = setTimeout(str,50);
If you want to proceed along the lines of using the timer to run the function at regular intervals, then you would need code similar to the following (hat-tip to #Sameera Thilakasiri for the inspiration):
function CountRemaining(string, targetcounter, limit){
var count = document.getElementById(string).value.length;
document.getElementById(targetcounter).innerHTML = ((limit-count) + " characters left");
}
setInterval(function() {
// call the function for each of the inputs on the page you need a counter for
CountRemaining('press-form-body', 'counter', 1000);
// etc
}, 50);
However, I believe #amchang87's approach is better overall, so I recommend you go with that if possible.
Tracking the number of characters left is always a little difficult. A good event to use is keyup or keypress, but that doesn't cover text that is dragged and dropped into the element, so people end up using a timer.
If you have many elements to monitor, consider putting them into an array, then call the timer at each interval and check all of the elements. Be careful with performance though, running the function every 50 ms may sap quite a bit of browser performance so try to keep the processing to an absolute minimum.
That means caching whatever you can and keep the logic simple.
Edit
The run and stop methods below could be used to start the timer when particular elements get focus, then stop it when they lose focus. That way you aren't hogging resources when not required.
/Edit
var keyCountCheck = (function() {
var elementArray, timerRef;
return {
// Initialise once
init: function() {
var input, inputs;
// Initialise elementArray if hasn't been done already
// If adding and removing elements, create new aray
// instead each time.
if (!elementArray) {
elementArray = [];
inputs = document.getElementsByTagName('input');
for (var i=0, iLen=inputs.length; i<iLen; i++) {
input = inputs[i];
if (input.type == 'text') {
elementArray.push(input);
}
}
}
timerRef = window.setInterval(keyCountCheck.run, 50);
},
// Run timer
run: function() {
// If setInterval not running, start it
if (!timerRef) {
keyCountCheck.init();
}
var el;
for (var i=0, iLen=elementArray.length; i<iLen; i++) {
checkLength(elementArray[i]);
}
},
// In case there is a reason to stop this thing.
stop: function() {
if (timerRef) {
window.clearTimeout(timerRef);
timerRef = null;
}
}
};
}());
window.onload = keyCountCheck.init;
function checkLength(el) {
// Character limit can be set as a data- attribute or
// class or various other ways. This is the simple way
var limit = 10;
var msgEl = document.getElementById(el.id + '_limitMsg');
if (msgEl) {
msgEl.innerHTML = (limit - el.value.length) + ' characters left. ' + (new Date());
}
}
Some supporting HTML to play with:
<input id="i0" value="1"><span id="i0_limitMsg"></span>
<br>
<input id="i1" value="2"><span id="i1_limitMsg"></span>
<br>
<button onclick="keyCountCheck.stop()">stop</button>
<button onclick="keyCountCheck.run()">run</button>
setInterval(
function CountRemaining(string, targetcounter, limit){
var count = document.getElementById(string).value.length;
document.getElementById(targetcounter).innerHTML = ((limit-count) + " characters left");
},50
);
Tryout this way.
Basic concept of this solution,
var f = function() {function_name(arg1); };
setTimeout(f, msec);
Related
This problem has really reminded me where I am regarding JS skills... :(
I feel I'm right on the cusp but I'm struggling to understand what to do conceptually, it's not so much a syntax issue as I feel I almost cracked it.
What I need
I'm trying to console.log a series of strings, one letter at a time.
There needs to be a delay between each LETTER outputted, say 300ms.
There must then be a delay between each STRING outputted, say 2000ms.
There are 2 strings in example array, but solution must support a dynamic number of strings.
My Current Code (can be pasted into a console)
var stringList = ['first test','second test'],
stringListLen = stringList.length;
for(var i = 0; i < stringListLen; i++){
// begin with first string and read it's length for iterations
var stringNumber = 0;
var currentString = stringList[stringNumber];
var currentStringLen = currentString.length;
// just see the word for clarification at this point in code
console.log(currentString);
(function (i) {
setTimeout(function () {
for (var j = 0; j < currentStringLen; j++) {
(function (j) {
setTimeout(function () {
// THE MAGIC HAPPENS HERE
console.log(j, currentString.charAt(j));
// End of string, so read next string, reset letter count
if(j === currentStringLen - 1){
stringNumber++;
currentString = stringList[stringNumber];
j = 0;
}
}, 300 * j); // each letter * specified delay
})(j);
};
}, currentStringLen * 300 * i); // letter * delay * letters in word
})(i);
}
The Issue
THE GOOD: I am successfully getting the short delay between letters outputted, and my check to switch to a new word and reset the letter counter when we get to the end of the first word is working...
THE BAD: I can't get the wait between the two words to work. I have tried a few ideas and have just got myself so confused I don't know if my approach is correct now.
THE UGLY: The final letter of the last term is also not outputting, and that is just totally unexpected.
What I've tried.
Okay, I've tried simply changing the "currentStringLen * 300 * i" elements to various combinations that seemed logical but had no effect better or worse. Ultimately I think I am trying to calculate "wait the number of letters in current string times 300 (the letter delay) * " <---- STRIKETHROUGH...
I actually don't know what I'm calculating and that's the issue.
I now think I want to split this into TWO functions, not two nested ones. One to READ AND PASS IN a string to another function that JUST outputs the letters with a short delay, then once it gets to the last letter it calls the first function asking for the next word. BUT then I'm still going to need to recurse for the number of strings in the array which creates the same issue...
Am I missing anything fundamental here people?
Is this roughly what you had in mind?
function printLetters(stringList) {
var wordIndex = 0,
letterIndex = 0;
printNext();
function printNext() {
var word = stringList[wordIndex];
var letter = word.charAt(letterIndex);
console.log(letter);
++letterIndex;
if (letterIndex === word.length) {
letterIndex = 0;
++wordIndex;
if (wordIndex < stringList.length) {
setTimeout(printNext, 2000);
}
return;
}
setTimeout(printNext, 300);
}
}
printLetters(['first test', 'second test']);
Here there's only ever one setTimeout running at once and a new one is being set as required with the appropriate time.
While I don't recommend having multiple timers running at once, it can be done. Something like this:
function printLetters(stringList) {
var letterCount = 0,
startTime = Date.now();
stringList.forEach(function(word, wordCount) {
word.split('').forEach(function(letter) {
setTimeout(function() {
console.log(letter, Date.now() - startTime);
}, wordCount * 1700 + (letterCount * 300));
++letterCount;
});
});
}
printLetters(['first test', 'second test']);
Here I've included the time delta in the logging to give a better sense of what is going on when. The gap between strings is 2000 but the constant in the code is 1700 because there's already 300 being added.
I would do a quite different approach. Rather than doing a bunch of precalculated timeouts and their associated closures, i would do just one timeout at a time, using recursion to then move on to the next timeout:
function delayShow(words) {
if (!words || words.length === 0) {
return;
} else if (words[0].length === 0) {
words.shift()
setTimeout(() => delayShow(words), 2000);
} else {
console.log(words[0].charAt(0));
words[0] = words[0].substr(1);
setTimeout(() => delayShow(words), 300);
}
}
delayShow(['first test','second test']);
You can use a condition inside the loop, and when you're on the last iteration of the strings characters, you call the recursive function again, inside a timeout, to iterate over the next string in the array etc.
var stringList = ['first test', 'second test'];
(function rec(j) {
var chars = stringList[j].split('');
chars.forEach(function(char, i) {
setTimeout(function() {
console.log(char);
// if it's the last iteration && there are more strings in the array
if ((i == (chars.length - 1)) && (j < stringList.length - 1)) {
setTimeout( function() {
rec(++j); // play it again
}, 2000);
}
}, i * 300);
});
})(0);
Even something as trivial as setTimeout's, is a good example of using async / await. So I've included an example below.
As you will see, the code is much easier to follow. With the added advantage of not creating multiple setTimeout's running concurrently,.
It also really helps make things much easier to change things, eg. If I asked you to alter the code below so that SPACE takes less time than other letters, to make it flow more naturally, it wouldn't take a lot of thought to work out what to change.
var
stringList = ['first test','second test'];
async function delay(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms);
});
}
async function run() {
let wordpos = 0;
let wordElem = document.querySelector('.words');
while (true) {
let word = stringList[wordpos];
let text = '';
for (var letterpos = 0; letterpos < word.length; letterpos ++ ) {
let letter = word[letterpos];
text = text + letter;
wordElem.innerText = text;
await delay(300);
}
await delay(2000);
wordpos = (wordpos + 1) % stringList.length;
}
}
run();
<h1 class="words">?</h1>
Hi I'm trying to recreate what http://austintexas.wayblazer.com/locations/austin-tx is doing for its search textbox - displaying typed character and rotating between several phrases.
My initial logic is to read the phrases from an array like so: [abc,123,XYZ] and then to use a split and forEach to separate the string into characters and create a typing effect using setTimeout.
However, when I attempt this logic, I seem to have trouble getting the desired effect. I've been reading through a lot of these questions on SO, but few of them address issues with nested for loops.
Desired results:
a b c (in a typing effect character by character)
clear textbox
1 2 3 (in a typing effect character by character)
clear textbox
X Y Z
Actual results:
abc123XYZ (abc shows up together at once, followed by 123, then XYZ)
$(document).ready(function() {
$('input#123').attr("placeholder", "");
var phrases = ['abc', '123', 'XYZ'];
for (var i = 0; i < phrases.length; i++) {
$('input#123').attr("placeholder", "");
(function(ind) {
var sentence = phrases[ind];
sentence.split("").forEach(function(char, index) {
setTimeout(function() {
$('input#123').attr("placeholder", $('input#123').attr("placeholder") + char);
}, ind * 500);
});
})(i);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<input id="123" />
Is there an elegant solution for what I want to achieve, and hopefully some explanation to enlighten me would be very much appreciated!
The great thing with javascript is closures, so you can create function closures, and push onto an array to act like a stack.. You can then pop these function off inside a setInterval..
$(document).ready(function(){
var input = $('input#123'),
phrases = ['Stack Overflow','Is the place you come','To get help with coding stuff'],
typedelay = 140,
waitops = 5,
cmds = [],
ph = input.attr.bind(input, 'placeholder');
function clear() {
ph('');
}
function addLetter(a) {
return function () {
ph(ph() + a);
}
}
function doLoop() {
cmds = [];
for (var i=0; i<phrases.length;i++) {
cmds.push(clear);
var sentence = phrases[i];
sentence.split("").forEach(function(char,index) {
cmds.push(addLetter(char));
});
//at the end of each sentence, there is a pause. lets do some no ops,.. 5*300 ms
for (var nn = 0; nn < waitops; nn ++) cmds.push(0);
}
//lets make this thing go on forever.. :)
cmds.push(doLoop);
}
doLoop();
var icheck = setInterval(function () {
//is there a cmd waiting, pop it and run it.
var cmd = cmds.shift();
if (cmd) { cmd(); }
}, typedelay);
input.focus(function () {
clearInterval(icheck);
cmds = [];
ph('What your want');
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<input id="123" />
Here's a recursive function that shifts the array and stops when array has no length
typePhrases(['Message #1','Another message','And the last one'], 300)
function typePhrases(phrases, speed){
// shift array to get word/phrase
var word = phrases.length && phrases.shift(), $input= $('#test').attr('placeholder','');
// don't do anything if no phrase/word
if(word){
word.split('').forEach(function(char, i){
setTimeout(function(){
// use attr(attributeName, function) to update
$input.attr('placeholder', function(_, curr){
return curr + char;
});
// on last letter call function again to get next word
if(i=== word.length-1){
setTimeout(function(){
typePhrases(phrases, speed);
},speed);
}
},i*speed)
});
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="test" placeholder=''>
The goal: When the page is loaded, display the image andy_black.jpg. After two seconds, change the image source, and the thus image in the browser, to a second image called andy_white.jpg. This will change back and forth every 2 seconds.
I checked out this article:
SetInterval function calls
(I searched other as well, with the tags [javascript] [function] and the word "setinterval", but most were using jQuery and my intention here is not to use any jQuery, it's an experiment in JavaScript after all).
which was quite helpful for before I had read it my code was much longer and the function was not called in the setInterval() function.
So here's some code:
Suggestions?
var i = 1;
function change_pic() {
i + 1;
if (i == 5) {
i = 1;
}
//I suspect the computer will read i as 5 for some
//tiny amount of time before reverting back to 1
//which I suspect could cause a further problem, but
//is it the source of the current issue?
if (i == 1 || i == 2) {
document.getElementById('img_to_flip').src = "https://cdns-images.dzcdn.net/images/artist/5d9e44027cc266260d7bd932d98f739d/500x500.jpg";
} else {
document.getElementById('img_to_flip').src = "https://media.s-bol.com/q7R3B8QVrAj2/550x549.jpg";
}
}
var pic_src = setInterval(change_pic, 2000);
<img id="img_to_flip" src="https://media.s-bol.com/q7R3B8QVrAj2/550x549.jpg" height="100" width="100" />
You forget to actually reassign the new value to i.
Either use:
i = i + 1;
or
++i;
Also, why count to five when you only have two states? A common paradigm to have an auto-resetting counter is to use modulo arithmetic:
i = (i + 1) % 2;
which guarantees that i will only ever have values of 0 or 1.
FWIW, here's an alternate way of writing the entire feature that'll work for any number of images - just populate the pics array:
(function() { // function expression closure to contain variables
var i = 0;
var pics = ["https://media.s-bol.com/q7R3B8QVrAj2/550x549.jpg", "https://cdns-images.dzcdn.net/images/artist/5d9e44027cc266260d7bd932d98f739d/500x500.jpg"];
var el = document.getElementById('img_to_flip'); // el doesn't change
function toggle() {
el.src = pics[i]; // set the image
i = (i + 1) % pics.length; // update the counter
}
setInterval(toggle, 2000);
})(); // invoke the function expression
<img id="img_to_flip" src="https://media.s-bol.com/q7R3B8QVrAj2/550x549.jpg" height="100" width="100" />
If you want to avoid the delay in first time setInterval call the function before the setInterval as shown in the top answer:
(function() { // function expression closure to contain variables
var i = 0;
var pics = [ "andy_white.jpg", "andy_black.jpg" ];
var el = document.getElementById('img_to_flip');
function toggle() {
el.src = pics[i]; // set the image
i = (i + 1) % pics.length; // update the counter
}
toggle()
setInterval(toggle, 2000);
})(); // invoke the function expression
I have web page with some really large tables that I'm filtering using some jquery routines I wrote. Anyway, when these tables get really large and the filtering functions can take some time to complete. So I figured I'd unhide a animated gif so the user had some feedback. However, the gif never appears when I call:
$('#loadingimg').show();
Unless I put an alert statement in front of it. I apologize for the ugly code, I'm not an experienced jquery/javascript programmer.
function filter()
{
var eles = ["mtmprogram","rate","stage"];
var tag;
var classes='';
$('#loadingimg').show();
//alert('hi');
$('.report').hide();
for (var i in eles)
{
tag = '#' + eles[i] + ' option:selected';
if ($(tag).val())
{
//$('.'+ $(tag).val()).show();
classes = classes + '.' + $(tag).val();
}
}
if (classes == '')
$('tr.report').show();
else
$(classes).show();
filterSubtables('Loan Number');
$('#loadingimg').hide();
}
Many thanks!
Maybe you aren't giving the #loadingimg element enough time to display. You could test this by running the rest of your code in a timeout:
function filter()
{
var eles = ["mtmprogram","rate","stage"],
classes = '';
$('#loadingimg').show();
//alert('hi');
setTimeout(function () {
$('.report').hide();
for (var i = 0, len = eles.length; i < len; i++)
{
var $tag = $('#' + eles[i] + ' option:selected');
if ($tag.val())
{
//$('.'+ $tag.val()).show();
classes = classes + '.' + $tag.val();
}
}
if (classes == '')
$('.report').show();
else
$(classes).show();
filterSubtables('Loan Number');
$('#loadingimg').hide();
}, 500);
}
Notice that I changed how the tag variable is used (this creates less CPU overhead to make less jQuery selections and to use as local a variable as possible). I also changed your loop to a better format that performs amazingly faster than for ( a in b ): http://jsperf.com/jquery-each-vs-for-loops/2
Is it possible to optimize this code? I have very low performance on this keyup event.
$('#opis').keyup(function () {
if ($('#opis').val() != "") {
var search = $.grep(
svgs, function (value) {
reg = new RegExp('^' + $('#opis').val(), 'i');
return value.match(reg) == null;
}, true);
$('#file_list').html("");
var tohtml = "";
$cnt = 0;
for (var i = 0; i < search.length; i++) {
if ($cnt <= 30) {
tohtml += "<li class='file_item'><a href='' class='preview'>" + search[i] + "</a> <a href='" + search[i] + "' class='print_file'><img src='img/add16px.png' alt='dodaj'/></li></a>";
$cnt++;
} else {
break;
}
}
$('#file_list').html(tohtml);
$(".preview").click(function () {
$('#file_preview').html('<embed src="opisy/' + $(this).html() + '" type="image/svg+xml" pluginspage="http://www.adobe.com/svg/viewer/install/" /> ');
$(".preview").parent().removeClass("selected");
$(this).parent().addClass("selected");
return false;
});
$(".print_file").click(function () {
if (jQuery.inArray($(this).attr('href'), prints) == -1) {
$('#print_list').append('<li>' + $(this).attr('href') + '</li>');
prints.push($(this).attr('href'));
} else {
alert("Plik znajduje się już na liście do wydruku!");
}
return false;
});
} else {
$('#file_list').html(" ");
}
});
var opis = $('#opis')[0]; // this line can go outside of keyup
var search = [];
var re = new RegExp('^' + opis.value, 'i');
for (var i = 0, len = svgs.length; i < len; i++) {
if (re.test(svgs[i])) {
search.push(svgs[i]);
}
}
It's up to 100x faster in Google Chrome, 60x in IE 6.
first thing you have to learn:
$('#opis').keyup(function() {
$this = $(this);
if($this.val()!=""){
// so *$this* instead of *$('#opis')*
// because you are reperforming a *getElementById("opis")* and you've already called it when you used the keyup method.
// and use $this instead of $(this) | pretty much the same problem
so about the grep function, maybe if you cache the results it would help in further searchs I guess, but I don't know if can help you with that
Well the thing with javascript is that it executes under the users environment and not the servers environment so optimization always varies, with large large arrays that need extensive work done on them to I would prefer to handle this server side.
Have you thought about serializing the data and passing them over to your server side, which would handle all the data calculations / modifications and return the prepared result back as the response.
You may also want to take alook at SE:Code Review for more optimization advise.
Some optimization, tips:
if($('#opis').val()!=""){ should be using '!=='.
return value.match(reg)==null; should be ===.
for(var i=0;i<search.length;i++){
reg = new RegExp(...); should be var reg ... as its not defined outside the function as a global.
Move all your variable declarations to the top of the function such as
var i,cnt,search,tohtml etc
i would advise you to start using Google Chrome, it has a built in system for memeory tracking on perticular tabs, you can go to the url about:memory in chrome, which would produce a result like so:
Image taken from: http://malektips.com/google-chrome-memory-usage.html
Each time you perform the grep, you are calling the 'matching' function once per array entry.
The matching function creates a RegExp object and then uses it to perform the match.
There are two ways you could improve this:
Create the RegExp once, outside of the function, and then use a closure to capture it inside the function, so that you don't have to keep recreating the object over and over.
It looks like all you're trying to do is to perform a case-insensitive tests to see whether the sought string is the start of a member of your array. It may be faster to do this more explicitly, using .toUpper and substring. However, that's a guess and you should test to find out.