Am using custom search filter
HtML
<input type="text" pInputText class="ui-widget ui-text" [(ngModel)]
="gloablFilterValue" (ngModelChange)="splitCustomFilter()" placeholder="Find" />
I am using ngModelChange() event on search box
globalSearch(realData, searchText, columns) {
searchText = searchText.toLowerCase();
return realData.filter(function (o) {
return columns.some(function (k) {
return o[k].toString().toLowerCase().indexOf(searchText) !== -1;
});
});
}
splitCustomFilter() {
let columns =
['PartNoCompleteWheel', 'DescriptionCompleteWheel', 'PartNoTyre', 'DescriptionTyre', 'PartNoRim', 'DescriptionRim','DeletedDateFromKDPStr', 'DateFromKDPStr', 'Status'];
this.tyreAndRimList = this.globalSearch(this.tyreAndRimList, this.gloablFilterValue, columns);
}
The this.tyreAndRimList list of values for the columns which is mentioned in a column variable.
Problem
The filter is working good! But the main problem is filter performance is very poor while the record count is huge(more than 100 rows per every column)
When
The filter is working good if am entering a single character (like a). But when I was typing the character continuously the browser is hanging. the reason is the filter has been firing every typing on the filter box(because of am using ngModelChange()// onchange() event)
What I want
I want to stop filtering if the user typing continuously on the search box. Once the user has stop the typing then only I need to start filtering.
What I did
I have tried to handle this by using setTimeout(). But it just wait the filter call for a second. It is working if the user entered just 2 or 3 character's continuously. But if the user typing more than 7 or 8 or above character's, it continues to hang the browser. because of many filter callbacks are processing on the same time.
setTimeout(() => //code of filtering ,1000);
Question
How to stop filtering while user continuously entering value and start the filtering once the user has been stop the typing?
I am working in angular-2 and typescript. But this question is not related with only for angularjs or angular or JavaScript or typescript because of I want an idea not a solution. So I'll add those all tags for this question. Don't remove it. Thanks
Debounce the function. See how underscore does it here: Function Debouncing with Underscore.js.
You would then generate a debounced version of your function like this:
var globalSearchDebounced = _.debounce(globalSearch, 100, false);
It will only call after the user has stopped typing for at least one second.
It's not possible to interrupt the Array.filter method. Based on what you need you could handle this like this:
let timerId = null
function handleChange() {
if(timerId) {
clearTimeout(timerId)
}
timerId = setTimeout(() => /* Array.filter(...) */, 1000)
}
Explanation
Have a variable which will contain the timerId returned from the setTimeout function. Every time the model get changed the handleChange function will be called (in this example). The function checks if the variable which contains the timerId is set and contains a timerId, when the variable contains the timerId the clearTimeout function will be called to clear the previous timeout after that the handleChange creates a new timeout and assigns the timerId (returned from the setTimeout function) to the variable.
Documentation for setTimeout
Documentation for clearTimeout
Without underscore, and without a Timeout (that will trigger the whole Angular lifecycle by the way), you can use an Observable with the async pipe and a debounce.
In your global search function :
return Observable.of(/* filter here and return the filtered array */).debounceTime(1000)
In your list (that has to be somewhere I guess)
<list-item *ngFor="let x of myFilteredResults | async">...</list-item>
I have complete it by using Subject to debounceTime.
private subject = new Subject<string>()
ngOnInit() {
this.subject.debounceTime(300).subscribe(inputText => {
this.gloablFilterValue = inputText;
this.splitCustomFilter(); // filter method
});
}
Now when I change the value in this.gloablFilterValue object by using change event. It just waiting for the end of event completion.
Related
I create a hmtl page with node and ejs with an unforseeable number of elements. I would like to create a setInterval for some, none or all of those elements, depending what the user is doing.
The problem is, that I am not able to create a dynamically variable for the setInterval so that I can later on cancel those Intervals.
Maybe I just need another simpler approach but at the moment I am stuck here.
camContainer.forEach(element => {
clearInterval(intervalVar);
if (!element.classList.contains("hidden")) {
countVisible++;
intervalVar = setInterval(showConsole, 1500);
} else {
countHidden ++;
}
count++;
})
I tried it with an Array instead of a regular variable but that didnt work either
intervalVar[count] = setInterval(showConsole, 1500);
You're on the right track with an array, but you need to push items onto it:
intervalVar = []
// ...
intervalVar.push(setInterval(showConsole, 1500))
When you want to cancel an interval, remove it from the array with slice or pop, depending on how you're selecting the item to cancel.
I am trying to create a search function in jQuery:
$('input').on('keyup', function(){
var searchTerm = $("input").val().toLowerCase();
$('.item').each(function(){
if ($(this).filter('[data-text *= ' + searchTerm + ']').length > 0 || searchTerm.length < 1) {
$(this).parent().show();
} else {
$(this).parent().hide();
}
});
});
Each time the user types in the input, it gets compared to the data attribute value of .item divs. If the data attribute of that element contains the search query, it gets displayed - otherwise hidden.
This works perfectly in Chrome, however it is really laggy in Safari for some reason when the user is typing.
Is there a way to fix this?
There are about 1400 divs (.item), and the data-text attribute is only around 10-20 characters for each element
Edit, fixed by removing .show() and .hide() - and replacing with native Javascript
Solution
I have face similar issue before, I think you might want to try adding something called "debounce", which basically add a delay before doing any process. In the keyup case, it will wait for the user to stop typing for any set amount of time (let's say 0.5 second) and then do the process (searches or whatever) If you don't use debounce, it will do the search every single time the user trigger the keyup event.
You can search for articles on how to do debounce, I think there's a lot of them. But in essence it uses the setTimeout and clearTimeout function of JS
Here's from the first article I found: https://levelup.gitconnected.com/debounce-in-javascript-improve-your-applications-performance-5b01855e086
const debounce = (func, wait) => {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
How to use this function? simple, just add your actual function (the search function) as the first parameter, and the delay (microseconds) in the second parameter, and then use the .call() function (why do this? because the debounce will return a function). So I guess something like this:
$('input').on('keyup', function(){
var searchTerm = $("input").val().toLowerCase();
debounce(function(){
$('.item').each(function(){
if ($(this).filter('[data-text *= ' + searchTerm + ']').length > 0 || searchTerm.length < 1) {
$(this).parent().show();
} else {
$(this).parent().hide();
}
});
}, 500).call();
});
This is how I will do it, because then I can add some stuff outside of the debounce into the keyup event, but you can just put the debounce returned function into a variable and then bind it with the keyup (like in the article), or just straight up put the debounce inside the keyup, like this:
$('input').on('keyup', debounce(function(){
...
},500));
How does it works?
You can read them in the articles, or find answer in StackOverflow, here's what I got Can someone explain the "debounce" function in Javascript
But if I'm using my own words, basically what you first need to understand is setTimeout set a timer before a function is called, and clearTimeout cancel that timer. Now in the debounce you can see that there's a clearTimeout before any setTimeout. So every time the keyup event is triggered it will basically cancel the last timeout set (if any), and then it will set a new timeout. In essence, it will reset the timer to what you set every time the event is triggered.
So for example:
The user want to search "abc"
They type "a" -> the debounce set a timer of 500ms before calling the
actual search of "a"
Before the 500ms is up, the user type "b", so the debounce cancel that "a" search, and search for "ab" instead, while also setting a timer of 500ms before doing it
Before the 500ms is up, the user type "c", so cancel the "ab" search, add a timer of 500ms to search for "abc"
The user stop typing until 500ms is up, now the debounce actually call the search for "abc"
What this results to? The heavy processing for the search is only done once for "abc", you can also put a loader or something to this heavy processing so it looks better for the user
Some quick fixes:
Collate the divs then show/hide in a single statement after the each rather than per iteration.
Changing the DOM is relatively expensive, so doing so in a single statement can greatly increase performance
If this is a table change to divs
Tables need to re-render the whole table on small changes. Fixed cell sizes can help. Not the case in this question, just a general improvement
Use an in-memory filter rather than read the DOM for each item.
Reading the DOM is much slower than in-memory (though in-memory uses more memory of course). For example, filter on .data() rather than [data-] as it will use in-memory. It's possible that this is quick in Chrome as Chrome may be caching the [data- attributes, so may not have an improvement in Chrome
debounce the input event so it only occurs when user has finished typing
Wait until the user has "finished" typing then run the action.
use operator associativity to your advantage
Although an edge case, this line
if ($(this).filter('[data-text *= ' + searchTerm + ']').length > 0 || searchTerm.length < 1)
will run the $(this).filter even when searchTerm.length < 1, change to
if (searchTerm.length < 1 || $(this).filter('[data-text *= ' + searchTerm + ']').length > 0)
Example showing this in action
function a() { console.log("a"); return true; }
function b() { console.log("b"); return true; } // b() doesn't need to exist
if (a() || b()) console.log("c")
consider server-side paging / filtering
substantially reduces the "footprint" on the page, so will be much quicker/more responsive, but with a potentially slightly longer delay retrieving the data. Depending on how it's displayed, 1400 records may be a lot for the user to view in one go (hence your filtering).
I'm trying since yesterday with my js code but it still doesn't work. So I have a select list, when I change the selected option it calls an onchange event that calls a DWR function.
The problem is the DWR function takes a while and resets my select options (the first element selected instead of the one selected), I tried to set the previous value but it works only when I add a while loop.
var valeurTypeUo = document.getElementById('selectElement').value;
// DWR function called by this function
forme.getOptions(params);
// console.log(document.getElementById('typesUoChoix').value) is empty String
while (document.getElementById('typesUoChoix').value != valeurTypeUo)
document.getElementById('typesUoChoix').value = valeurTypeUo;
This is my code, it works but there is always an alert if I want to stop the script. Is there any way to replace this while instruction?
You can use setInterval which you can clear instead of while.
Your code can be like:
var stopCheking = false;
var checkingInterval; // interval holder
var valeurTypeUo = document.getElementById('selectElement').value;
// DWR function called by this function
forme.getOptions(params);
// console.log(document.getElementById('typesUoChoix').value) is empty String
checkingInterval= setInterval( function() {
if( stopCheking ) clearInterval(checkingInterval);
}, 2);
When you want to stop the event (interval) in this case, you set the stopCheking flag to true.
You can "pause" functions with ES6 generators(yield keyword), but normally you should not need it in this case. Plain callback on the async call should do it.
Anyways here is the link for generators: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
I am trying to filter data using .keypress. My current approach is to capture the data from .keypress and pass to my filter function. I am successful in returning data that begins with a letter. So if I type A into my field it will return all cats that begin with A. I would like to narrow my search by typing in more letters and have it update each time. Is there a simple solution here? Here is the code
// retrieve data from key press
$("input").keypress(function (e) {
if (e.which !== 0 && e.charCode !== 0) {
var criteria = String.fromCharCode(e.keyCode|e.charCode);
}
$.getJSON('cats.json', function(cats) {
filterCats(cats, criteria);
});
});
// filter function
function filterCats(cats, criteria){
$.getJSON('cats.json', function(cats) {
//var filteredData = cats.filter(function(c){return c.breed.toUpperCase().indexOf(criteria.toUpperCase()) !== -1;});
var filteredData = cats.filter(function(c){return c.breed.toUpperCase().indexOf(criteria.toUpperCase()) === 0 ;});
renderData(filteredData);
});
}
As others mentioned, keyup is better because it fires after the key is released.
keydown
Fires when the user depresses a key. It repeats while the user keeps the key depressed.
keypress
Fires when an actual character is being inserted in, for instance, a text input. It repeats while the user keeps the key depressed.
keyup
Fires when the user releases a key, after the default action of that key has been performed.
Above from http://www.quirksmode.org/dom/events/keys.html
Also as the other answer states, running your code immediately after every key press could result in a lot of requests sent to the server.
Instead, try throttling it with a timeout.
var timeout, criteria;
$('input').keyup(function(e) {
clearTimeout(timeout);
timeout = setTimeout(function() {
criteria = this.value;
$.getJSON('cats.json', function(cats) {
filterCats(cats, criteria);
});
}.bind(this), 125);
});
As Andy pointed out in a comment, you need to use the value of the input, since you are currently looking at a single character upon each character entry. As Hossein Shahsahebi pointed out, keyup would be a more accurate event for what you desire to do. Also, do not forget about pasting, which should trigger the same logic.
I would like to add that I believe it is undesirable to do the searching upon each character entry. Imagine someone typing really quickly. You will have a lot of requests sent to the server. I would rather wait for a while before doing the search. When the event is triggered, store the current time in a variable and use setTimeout to wait for a second or so before calling the filterCats function. Whenever the event runs, override that variable to the current moment. When filterCats is executed, check whether the variable's value is old-enough. If not, return without filtering. If so, do the filtering. This will decrease server load due to this feature drastically.
I have a function which does something async like saving to database. Want a mechanism that first inserts the row and the next insertion should occur only when the first insert operation has finished.
Here is what I have tried and it somewhat works.
var interval = true;
function insert() {
model.save(function () {
interval = true;
})
}
foreach(row, function (key, val) {
var interval1 = setInterval(function () {
if (interval) {
insert();
interval = false;
clearInterval(interval1);
}
}, 100)
})
Is it the correct approach of doing this? Please shed some light about my understanding of timers in javascript.
No, you should not be creating timers to poll for when something is done. That's probably the worst way you can do it. What you want to do is to explicitly start the next iteration each time the previous one finishes.
Here's the general idea for how you do this without polling. The idea is that you need to create a function that can be called successive times and each time it's called, it will perform the next iteration. You can then call that function from the completion handler of your async operation. Since you don't have a nice convenient foreach loop to control the iteration, you then have to figure out what state variables you need to keep track of to guide each iteration. If your data is an array, all you need is the index into the array.
function insertAll(rows) {
// I'm assuming rows is an array of row items
// index to keep track of where we are in the iteration
var rowIndex = 0;
function insert() {
// keep going as long as we have more rows to process
if (rowIndex < rows.length) {
// get rows[rowIndex] data and do whatever you need to do with it
// increment our rowIndex counter for the next iteration
++rowIndex;
// save and when done, call the next insert
model.save(insert)
}
}
// start the first iteration
insert();
}
If you don't have your data in an array that is easy to step through one at a time this way, then you can either fetch each next iteration of the data when needed (stopping when there is no more data) or you can collect all the data into an array before you start the operation and use the collected array.
No, this is absolutely not the right way to do this. Lets assume that row contains 10 values, then you are creating 10 independent timers which continuously run and check whether they can insert. And it's not even guaranteed that they are executed in the order they are created.
As jfriend00 already mentioned, you should omit the "loop" and make use of the completion callback of the save operation. Something like this:
var rows = [...];
function insert(rows, index) {
index = index || 0;
var current_element = rows[index];
model.save(function() {
if (index < rows.length - 1) {
insert(rows, index + 1);
}
});
}
insert(rows);
Notice how the function calls itself (somehow) after the save operation is complete, increasing the index so the next element in the array is "saved".
I would use a library that handles async stuff such as async.js
BTW it seems like your model.save methods takes a callback, which you can use directly to call the insert method. And if the insert function is one you have made by yourself, and not a part of some bigger framework, I will suggest to re-write it and make take a callback as parameter, and use that instead of using setInterval for checking when async work is done.