Abandon pending/running keyup functions if another key is pressed - javascript

I have a search box that hides all lines in a list that don't contain the entered text.
This worked great until the list became 10,000 lines long. One keystroke is fine but if the user types a several letter word, the function is iterated for each keypress.
What I want to do is to abandon any previous execution of the function if a new key is pressed.
The function is very simple, as follows:
$("#search").keyup(function(e) {
goSearch();
});
function goSearch()
{
var searchString = $("#search").val().toLowerCase();
$(".lplist").each(function(index, element) {
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(searchString,0) != -1)
$(row).show();
else
$(row).hide();
});
}
Thanks

You can't directly. Javascript is not multi-threaded so your function will run and block any key-presses until it is done.
The way this is made tolerable from a user-experience point of view is to not trigger a function immediately on a key event, but to wait a short period of time and then fire the event.
While the user is typing, the timeout function will continually be set and reset and so the gosearch function won't be called, and so the user won't have their typing interrupted.
When the user pauses typing, the timeout will countdown to zero and call the search function, which will run and block typing until it completes. But that's okay (so long as it completes within a second or so) as the user is probably not currently trying to type.
You can also do what you actually asked by breaking up your gosearch function into chunks, where each call to the function: * Reads a counter of the number of lines processed so far, and then processes another 500 lines and increments the counter. * Calls another gosearch using setTimeout with a value of zero for the time. This yields events to other 'threads', and allows for fast changing of search terms.
var goSearchTimeout = null;
var linesSearched = 0;
function keySearch(e){
if(goSearchTimeout != null){
clearTimeout(goSearchTimeout);
linesSearched = 0;
}
goSearchTimeout = setTimeout(goSearch, 500);
}
$("#search").keyup(keySearch);
function highLight(index, element) {
if(index >= linesSearched){
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(searchString,0) != -1){
$(row).show();
else{
$(row).hide();
}
if(index > linesSearched + 500){
linesSearched = index;
goSearchTimeout = setTimeout(goSearch);
return;
}
}
function goSearch(){
goSearchTimeout = null;
var searchString = $("#search").val().toLowerCase();
$(".lplist").each(highLight);
}
If you're going to use timeout callbacks like this, I'd strongly recommend wrapping your code up into jQuery widgets, so that you can use variables on the object to store the variables goSearchTimeout etc rather than having them float around as global variables.

Introduce a counter var keypressCount that is being increased by your keypress event handler. at the start of goSearch() write its value into a buffer. Then at each run of your $(".lplist").each() you ask if the current keypressCount is the same as the buffered one; if not, you return. I would suggest you use a for() though since it is easier to break; than $.each().
Update:
You will have to make sure that there is time for new keypress events to be fired/received, so wrap the anonymous function of your $.each() inside a timeout.
Reference: http://www.garrickcheung.com/javascript/what-i-learned-about-multi-threading-in-javascript/

You can use a global variable to save search string and stop execution when search string changes.
IMPORTANT: You must set a timeout in each iteration so that function execution is paused and global variables are updated, as JavaScript is single-threaded.
Your code would look like this:
var searchString;
$("#search").keyup(function(e) {
// Update search string
searchString = $("#search").val().toLowerCase();
// Get items to be searched
var items = $(".lplist");
// Start searching!
goSearch(items, searchString, 0);
});
function goSearch(items, filter, iterator)
{
// Exit if search changed
if (searchString != filter) {
return;
}
// Exit if there are no items left
if (iterator >= items.length) {
return;
}
// Main logic goes here
var element = items[iterator];
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(filter, 0) != -1)
$(row).show();
else
$(row).hide();
// Schedule next iteration in 5 ms (tune time for better performance)
setTimeout(function() {
goSearch(items, filter, iterator + 1);
}, 5);
}

Related

Javascript function that runs a part of its code only once without the use of external code

Trying to make a function that runs on its own using private variables. Version 1 turned out pretty good, but for it to work I have to input 2 parameters and use
Other things that I've tried are in version 2 and 3 where the variables aren't global, but it'd great if they all were inside the function.
// Independent functions
// description: Attempt to create a function that runs a part of its code
// only once, and later ignores it. The function needs to run without the use of external code (like global variables).
[Update] It finally worked. Huge thanks to ocBiermann, for mentioning that functions in javascript are also objects. Here's how you do it with arrays.
function Ind(x) {
if (Ind.random === undefined) {
sum = [];
Ind.random = 2;
}
if (Ind.random === 2) {
sum.push(x);
x--;
if (x != 0) {
Ind(x);
}
return sum;
}
}
console.log(Ind(10));
// Here's how I did it with switch (The code is longer though).
function Index(start, end) { // 1) Start and end are used as input
switch (start) {
case "Second_run": // 4) Second time running
output = []; // 5) declares output array
start = "Third_run";
Index(start, end)
break;
case "Third_run": // 6) Third time running.
save++;
output.push(save); // 7) The value of save is saved in output
if (save == end) { // 8) Functions is re-executed, conditions met
return output
}
Index(start, end)
break
default: // 2) The function starts here and saves start's value as save
save = start;
start = "Second_run"; // 3) It chages start's value to "Second_run"
Index(start, end)
break;
}
return output
}
console.log(Index(0, 10));
// Here's the first one, but with start and end (same concept with small changes)
function Ind(start, end) {
if (Ind.random === undefined) {
output = [];
Ind.random = 2;
}
if (Ind.random === 2) {
output.push(start);
start++;
if (start <= end) {
Ind(start, end);
}
return output;
}
}
console.log(Ind(10, 15));
It looks like you are trying to make the function avoid executing certain code based on the result of previous executions WITHOUT using global variables or parameters.
Remember that in JavaScript, a function is also an object. This means that a function can have properties, much like the object of a class.
So the nice thing is you can access and set properties each time your function is executed, and then execute the code you want based on that.
Here is a pretty simple example:
function Independent()
{
/*First check if the property has already been defined */
if (Independent.SomeProperty === undefined) {
Independent.SomeProperty = 1;
console.log("This is the first time Independent() is being called");
}
else if (Independent.SomeProperty === 2) {
console.log("This is the second time Independent() is being called");
//Your code here
}
else if (Independent.SomeProperty === 3) {
console.log("This is the third time Independent() is being called");
//Your code here
}
//etc.
Independent.SomeProperty++; //Increment your property here.
}
You could even use a switch statement if your function will be called more than a few times. Or you could make the value wrap around back to 0 after the function has been called a certain number of times. There are many other possibilities as well. It just depends on your specific requirements.

javascript, user clicks too fast

i'm new to javascript, and i'm trying to build some kind of memory game.
the game works fine until the user clicks too fast on the cards and more than 2 cards are "open".
the function is activated by clicking. i tried to check if the function is already active by adding a global var, set it to 1 ( function busy) at entrance and set it back to 0 (free) at the end. it didn't work.
any ideas how to solve it?
code is:
var isProcessed =0;
function cardClicked(elCard){
//check to see if another click is being processed
if(isProcessed===1){
return;
}
//if function is not already active - set it to "active" and continue
isProcessed=1;
//doing all kind of stuff
//setting function to "free" again
isProcessed=0;
}
I believe the problem with your code is that when you call the function it both processes and frees the card currently being clicked which allows other cards to be clicked as well.
A simple way to fix it is: (I'm assuming after two cards are clicked it will "close" and others will be available)
var isProcessed =0;
var selectedPair=[];
function cardClicked(elCard){
//add to the amount of cards processed
isProcessed++;
//If there are two cards "processed" then:
if(isProcessed===2){
//reset the amount processed after two cards have been opened
isProcessed=0;
//"close" card functionality
//clear the array of selected cards;
selectedPair=[];
return;
}else{
//add card to the selectedPair array so we can keep track
//which two cards to "close" after it resets
selectedPair.push(elCard);
//do all kinds of stuff
}
}
Your plan should work. As #JustLearning mentioned in the comment, it might be better to to disable the button instead of using a flag variable. This will offer visual clues to the user that they can't click.
Having said that, the important thing is that resetting your flag, or enabling he button, has to happen after //doing all kind of stuff is done.
Assuming that //doing all kind of stuff is something slow and asynchronous this means resetting it in the callback or when a promise resolves.
Here's a quick example that asynchronously runs a count. During the count isProcessing is set to true. In the callback function — not after it — it resets the flag.
function someSlowThing(cb){
let n = 30
let i = 0
let d = document.getElementById('count')
let itv = setInterval(() => {
if (i > n) {
clearInterval(itv);
i = 0
return cb()
}
d.innerHTML = i++
}, 50)
}
var isProcessing = false;
function process(e){
if(isProcessing) {
console.log("wait please")
return
}
isProcessing = true
someSlowThing(() => {
isProcessing = false // <-- important that the happens in the callback
console.log('done')
})
// don't try to set isProcessing here, it will happen too soon.
}
<div id = 'count'>0</div>
<button onclick='process()'>start</button>

How do I create and focus a new element in AngularJS?

I have a form with a dynamic number of inputs, controlled by AngularJS.
<body ng-app="mainApp" ng-controller="CreatePollController" ng-init="init(3)">
<form id="createPollForm">
<input class="create-input" ng-repeat="n in questions" id="q_{{$index}}" name="q_{{$index}}" type="text" ng-keypress="createInputKeypress($event);"/>
Add Question
</form>
</body>
This is being controlled by the following angular code:
app.controller('CreatePollController', function($scope) {
$scope.questions = [];
$scope.init = function(numOfInputs){
for(var i = 0; i < numOfInputs; i++){
$scope.questions.push({
"questionText":""
});
}
};
$scope.addQuestion = function(){
$scope.questions.push({
"questionText":""
});
};
$scope.createInputKeypress = function(e){
if(e.keyCode === 13){
e.preventDefault();
var idx = Number(e.target.id.replace("q_", ""));
if(idx === this.questions.length - 1){
this.addQuestion();
}
// Wait for angular update ????
var nextId = "#q_" + (++idx);
$(nextId).focus();
}
};
});
Currently, when the user hits the Enter key while focused on a text input, the createInputKeypress function is called and the browser focuses the next input in the form. However, if you are currently focused on the last element in the form, it adds a new question to the questions array, which will cause another input to be generated in the DOM.
However, when this new element is created, the focus() call isn't working. I suspect this is because angular doesn't add the new element right away, so trying to use jQuery to locate and focus the new element isn't working.
Is there a way to wait for the DOM to be updated, and THEN focus the new element?
As you might already know, javascript is turn based, that means that browsers will execute JS code in turns (cycles). Currently the way to prepare a callback in the next javascript cycle is by setting a callback with the code we want to run on that next cycle in a timeout, we can do that by calling setTimeout with an interval of 0 miliseconds, that will force the given callback to be called in the next javascript turn, after the browser finishes (gets free from) the current one.
Trying to keep it simple, one browser cycle executes these actions in the given order:
Scripting (where JS turn happen)
Rendering (HTML and DOM renderization)
Painting (Painting the rendered DOM in the window)
Other (internal browser's stuff)
Take a look at this example:
console.log(1);
console.log(2);
setTimeout(function () {
console.log(3);
console.log(4);
}, 0);
console.log(5);
console.log(6);
/** prints in the console
* 1 - in the current JS turn
* 2 - in the current JS turn
* 5 - in the current JS turn
* 6 - in the current JS turn
* 3 - in the next JS turn
* 4 - in the next JS turn
**/
3 and 4 are printed after 5 and 6, even knowing that there is no interval
(0) in the setTimeout, because setTimeout basically prepares the given callback to be called only after the current javascript turn finishes. If in the next turn, the difference between the current time and the time the callback was binded with the setTimeout instruction is lower than the time interval, passed in the setTimeout, the callback will not be called and it will wait for the next turn, the process repeats until the time interval is lower than that difference, only then the callback is called!
Since AngularJS is a framework wrapping all our code, angular updates generally occur after our code execution, in the end of each javascript turn, that means that angular changes to the HTML will only occur after the current javascript turn finishes.
AngularJS also has a timeout service built in, it's called $timeout, the difference between the native setTimeout and angular's $timeout service is that the last is a service function, that happens to call the native setTimeout with an angular's internal callback, this callback in its turn, is responsible to execute the callback we passed in $timeout and then ensure that any changes we made in the $scope will be reflected elsewhere! However, since in our case we don't actually want to update the $scope, we don't need to use this service, a simple setTimeout happens to be more efficient!
Knowing all this information, we can use a setTimeout to solve our problem. like this:
$scope.createInputKeypress = function(e){
if(e.keyCode === 13){
e.preventDefault();
var idx = Number(e.target.id.replace("q_", ""));
if(idx === this.questions.length - 1){
this.addQuestion();
}
// Wait for the next javascript turn
setTimeout(function () {
var nextId = "#q_" + (++idx);
$(nextId).focus();
}, 0);
}
};
To make it more semantic, we can wrap the setTimeout logic
in a function with a more contextualized name, like runAfterRender:
function runAfterRender (callback) {
setTimeout(function () {
if (angular.isFunction(callback)) {
callback();
}
}, 0);
}
Now we can use this function to prepare code execution in the next javascript turn:
app.controller('CreatePollController', function($scope) {
// functions
function runAfterRender (callback) {
setTimeout(function () {
if (angular.isFunction(callback)) {
callback();
}
}, 0);
}
// $scope
$scope.questions = [];
$scope.init = function(numOfInputs){
for(var i = 0; i < numOfInputs; i++){
$scope.questions.push({
"questionText":""
});
}
};
$scope.addQuestion = function(){
$scope.questions.push({
"questionText":""
});
};
$scope.createInputKeypress = function(e){
if(e.keyCode === 13){
e.preventDefault();
var idx = Number(e.target.id.replace("q_", ""));
if(idx === this.questions.length - 1){
this.addQuestion();
}
runAfterRender(function () {
var nextId = "#q_" + (++idx);
$(nextId).focus();
});
}
};
});

Exit the loop abruptly

Let's imagine the following code:
function DoSomethingHard(parameter1, parameter2, parameter3){
// Do Something Hard Here
}
var i;
for(i = 0; i <= stuff.length; i++) {
// "stuff" is an array
DoSomethingHard(stuff[i].something1, stuff[i].something2, stuff[i].something3);
}
$( "#button_to_cancel" ).click(function() {
//something to cancel
});
Suppose the array "stuff" has 100 positions, so the for loop will run
100 times, ie, it will do "Do Something Hard" 100 times.
Let's also consider that "DoSomethingHard" takes about 5 seconds to run
completely.
My question is: How do I manage the cancellation of "DoSomethingHard"? For example, if it has already run 50 times, how can I cancel the subsequent executions through a button? I did not succeed in my attempts and it always ends up running the whole loop ....
Thanks in advance :)
Javascript is single threaded, and a for loop executes until it is finished. I would do something like this to allow time for the cancel.
function DoSomethingHard(param){
//do something
}
var i = 0;
var loopInterval = setInterval(function() {
if (i >= stuff.length) {
clearInterval(loopInterval);
return;
}
DoSomethingHard(stuff[i]);
i++
}, 10);
$( "#button_to_cancel" ).click(function() {
clearInterval(loopInterval);
});
You can make use of setInterval to call the function and when you have a click event you can clear the intervals
var mytimeout;
var i;
for(i = 0; i <= stuff.length; i++) {
// "stuff" is an array
mytimeout = window.setInterval(DoSomethingHard(stuff[i].something1, stuff[i].something2, stuff[i].something3), 2000);
}
$( "#button_to_cancel" ).click(function() {
//something to cancel
window.clearInterval(mytimeout)
});
Simplest way as I see it:
function DoSomethingHard(parameter1, parameter2, parameter3){
//Do Something Hard Here
}
var i;
var active = true; //as of now, we want to process stuff
for(i=0;i<=stuff.length;i++){
//"stuff" is an array
if(active){
DoSomethingHard(stuff[i].something1, stuff[i].something2, stuff[i].something3);
}else {
active = true; //reset active in case we want to run loop again later on
break; // break out of loop
}
}
$( "#button_to_cancel" ).click(function() {
active = false;
});
You can't easily cancel it with a click on a button, unless you use recursion or iterators instead of a loop.
But you can cancel the loop inside itsself with a break; statement when some condition is met. For example you could write:
var result;
for(i=0;i<=stuff.length;i++){
result = DoSomethingHard(stuff[i].something1, stuff[i].something2, stuff[i].something3);
if (result === 'error' || i === 50) break;
}
This will end the loop if result becomes the 'error' (or anything else your return from inside the function) or when i reaches 50.
Now that i think of it, it's possible with a button click, but it requires more code and is inefficient. give me a minute.
Update: I would not advice this ppttern either, but it's pretty flexible:
var exitCondition,
doSomethingHard = function doSomethingHard(parameter1, parameter2, parameter3){
// Do Something Hard Here
},
i,
length = stuff.length,
result;
for (i = 0; i <= length; i++) {
// "stuff" is an array
result = doSomethingHard(stuff[i].something1, stuff[i].something2, stuff[i].something3);
if (exitCondition( result, i )) break;
}
$( "#button1_to_add" ).click(function() {
exitCondition = function( result, index ) {
return index === 50;
});
});
$( "#button2_to_cancel" ).click(function() {
exitCondition = null;
});
The clue here is to have an exit condition (or multiple) that you check inside the loop and have the button update this condition.
You can not stop a for loop from UI interaction because everything is running in a single thread and your action will execute only after loop executes completely. You can use setInterval as #jason p said.
I solve this way:
function DoSomethingHard(parameter1, parameter2, parameter3){
//Do Something Hard
var timer = window.setInterval(function(){
var page = $$('.page').data('page');
console.log("The page is: "+page);
if(page != 'preview'){
//Cancel...
console.log('Aborted');
clearInterval(timer);
}
},1000);
}
That is, i changed the scope. Instead using button click, i monitered when user leave the page, so cancel it.
You need to note that loop is synchronous where as your function isn't. Loop won't wait for DoSomethingHard() to compleye before the next iteration begins.In just a few milliseconds DoSomethingHard has been called over a hundred times! and your loop gets over so in essence break is of no use here.I think no language construct can help here
So what to do?
You need to decide whether to do something or not in the function itself create a global flag and check it inside the function
function DoSomethingHard(){
if(flag50done){
return;
}else{
//do what this fn was meant for
}
}
You can change value of flag50done with a click of a button and further actions would get stopped due to the return
In case DoSomethingHard is some 3rd party function which you cannot modify you can wrap it in another function say runDecider
function runDecider(a,b,c){
//add flag check here
if(flag50done){
return;
}else{
DoSomethingHard(a, b, c);
}
}
and call this in the loop
var result;
for(i=0;i<=stuff.length;i++){
result = runDecider(stuff[i].something1, stuff[i].something2, stuff[i].something3);
}

Is it more efficient to use a bunch of setTimeouts or a setInterval for refreshing a bunch of objects

Let's say there is a set of Watchers that need to be refreshed periodically. They each may have a different refresh interval. There may be several hundred such Watcher items at any given moment. The refresh time for any Watcher can range from a second to several minutes or hours.
Which is better?
Use a separate setTimeout for each one.
Use a setInterval that runs a function every second. The functions then cycles through each Watcher checking to see if it needs to be refreshed.
At first I assumed that the native code implementation of setTimeout would be more efficient than a JS function that does checking, but it's really a question of how setTimeout is implemented, how much overhead each timeout takes on a per-tick basis, and how well the number of timeouts scales.
I'm asking this for a Node application so the specific engine I'm referring to is V8, but it'd be cool if anyone knows the details for other engines as well.
Here's one idea that should be pretty efficient regardless of how setTimeout or setInterval is implemented. If you have N events scheduled for N different times in the future, create an array of objects where each object has a property for the time that the event is due and a property that tells you what type of event it is (a callback or some other identifier). Initially sort that array by the time property so the next time is at the front of the event and the furthest time is at the end.
Then, look at the front of the array, calc the time until that event and do setTimeout() for that duration. When the setTimeout() fires, look at the start of the array and process all events who's time has been reached. If, after processing an event, you need to schedule it's next occurrence, calc the time in the future when it should fire and walk the array from start to finish until you find an event that is after it and insert this one right before that event (to keep the array in sorted order). If none is found, insert it at the end. After processing all events from the front of the array who's time has been reached, calc the delta time to the event at the front of the array and issue a new setTimeout() for that interval.
Here's some pseudo-code:
function orderedQueue() {
this.list = [];
}
orderedQueue.prototype = {
add: function(time, callback) {
var item = {}, added = false;
item.time = time;
item.cb = callback;
for (var i = this.list.length - 1; i >= 0; i--) {
if (time > this.list[i].time) {
// insert after the i item
this.list.splice(i + 1, 0, item);
added = true;
break;
}
}
// if no item was after this item,
// then put this on the front of the array
if (!added) {
this.list.unshift(item);
}
},
addDelta(delta, callback) {
var now = new Date().getTime();
this.add(now + delta, callback);
},
waitNext: function() {
// assumes this.list is properly sorted by time
var now = new Date().getTime();
var self = this;
if (this.list.length > 0) {
// set a timer for the first item in the list
setTimeout(function() {
self.process();
}, this.list[0].time - now);
}
},
process: function() {
var now,item;
// call all callbacks who's time has been reached
while (this.list.length) {
now = new Date().getTime();
if (this.list[0].time <= now) {
// remove front item from the list
item = this.list.shift();
// call the callback and pass it the queue
item.cb(this);
} else {
break;
}
}
// schedule the next item
this.waitNext();
}
}
And, here's generally how you would use it:
var q = new orderedQueue();
// put initial events in the queue
q.addDelta(100, f1);
q.addDelta(1000, f2);
q.addDelta(5000, f3);
q.addDelta(10000, f4);
q.addDelta(200, f5);
q.addDelta(100, f1);
q.addDelta(500, f1);
// start processing of queue events
q.waitNext();

Categories