Is there any way to optimize my client side search function? - javascript

I've created a page that needs to show all records from a table, instead of paginating.
The problem is that my search function actually takes about 10 seconds to run for 1300 records. Is there any way to optimize it?
I'm using bootstrap d-none class to hide elements.
if (document.getElementById("form_search_user")) {
document.getElementById("form_search_user").addEventListener("submit",function (event) {
event.preventDefault();
let start = performance.now();
let search = document.getElementById("search_input").value;
let table_row = document.querySelectorAll(".user_table tbody tr");
table_row.forEach(element => {
element.innerText.toUpperCase().indexOf(search.toUpperCase()) > -1 ?
element.classList.remove("d-none") : element.classList.add("d-none");
});
console.log(((performance.now() - start) / 1000).toFixed(3));
})
}

Do not use .innerText which is known to be slow because it has to take styling into account. You probably want to search in .textContent instead.

Related

jQuery: How do I check a number from string?

there is a class named:
<div class="level_11 price_level" style="display: block;">
I have a script that runs a browser function.
But I want to run this only when my number in the script is lower than the number from the "level_" div.
I have no idea how to do this.
Well, there are everytime another number. Sometimes level_4, sometimes level_18, etc.
I need to check the number and say if my number is lower then the number from the level_, then run the script.
let setLevel = 3; // Change this to set the building level. Example: let Level = 20 //
let Level = setLevel -1; // Don't touch this //
let logLevel = Level +1; // Don't touch this //
console.log(`Success ✓ - ${IBuilding.length} buildings left`);
$.each(IBuilding, function(Index, Entity) {
let BuildingMissing = IBuilding.length - (Index + 1);
window.setTimeout(function() {
$.get(`/buildings/${Entity.id}/expand_do/credits?level=${Level}`)
console.log(`${BuildingMissing > 0 ? BuildingMissing : 'Success ✓ - last building successfully expanded to level: ' + logLevel }`);
}, Index * 250);
});
});
Basically the script request all sites, and every site have another "level_".
On the sites where the "level_" number is higher than the number in my variable, then dont run the script at the site. but run the script at the sites where my number is higher then the "level_"
Can anyone help me out? :/
You can get the level number like this:
let classname = $("div[class^='level']").attr("class").split(" ").filter(getClass);
function getClass(value) {
return value.startsWith("level_");
}
let level = classname.toString().substr(6);
console.log(level);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="level_11 price_level" style="display: block;">
I found this on link /buildings/ID/:
current level of building
Maybe I can check the Level from there?
The HTML section for that is:
<dd>
13
Ausbauen
</dd>

jquery - add multiple timers associated to HTML divs

I have the following DIV containing multiple cards elements:
Each of those cards have the following HTML structure:
<div class="user-w">
<div class="avatar with-status status-green">
<img alt="" src="img/avatar1.jpg">
</div>
<div class="user-info">
<div class="user-date">
12 min
</div>
<div class="user-name">
John Mayers
</div>
<div class="last-message">
What is going on, are we...
</div>
</div>
</div>
Those cards are loaded dynamically using ajax. What I need is to attach to each <div class="user-w"> a stopwatch so I can change for example background color when elapsed time is 4 min or make it hidden when elapsed time reaches 6 min.
I was thinking on using SetInterval multiple times for I think this is not possible.
Each DIV card element should be totally independant in terms of timing from the others.
Any clue on how to do it correctly?
When you build the card from the ajax object, set a data element to store the timestamp on the card. Use setInterval to trigger a function that loops through all of the cards and checks their timestamps against the current time and updates the date on the ui, changes the bgcolor, or removes the element altogether.
In general, shy away from the "attach everywhere" syndrome. Think in lists, and simple processors. You will thank yourself down the road, as will your users for more efficient code, and your maintenance programmer.
Accordingly, one thought process might be to setup an array of the elements in question, and use a single setInterval. Something like:
...
var cardList = [];
function processCards ( ) {
var i, cardEl, cardStartTime, now;
now = Date.now();
i = -1;
while ( ++i < cardList.length ) {
cardEl = cardList[ i ][ 0 ];
cardStartTime = cardList[ i ][ 1 ];
if ( cardStartTime + 6 min < now ) {
// do 6 minute thing
}
else if ( cardStartTime + 4 min < now ) {
// ...
}
}
}
$.get('/your/new/cards/call')
.done(function(...){
...
var now = Date.now();
for ( i in returnedCards ) {
cardElement = however_you_create_your_element;
attach cardElement to the DOM
// save reference to element and time created for later processing
cardList.push([cardElement, now]);
}
});
setInterval(processCards, 2*60*1000); // or whatever granularity you want.
One advantage of this approach over multiple setTimeout calls for each card is the simplicity of having a single processing function, rather than N copies lying around. It's easier to reason about and manage, and reduces the likelihood of errors if an element disappears before it's associated setTimeout function executes.
One way to do this is, after your AJAX call completes and the DOM has been updated, you can use jQuery to select your cards and for each card you can:
Get the time value and parse it to convert it to milliseconds - you can write a simple helper function for this or use something like momentjs based on how complex your requirement is
Use setTimeout with the parsed value and do any style updates/hiding as needed
Sample Code:
$('.user-w').each(function(i, el){
var $el = $(el);
var val = $el.find('div.user-date').html();
val = parseTime(val) // Assuming a function to parse time from string to milliseconds is there
setTimeout(function(){
// Do any updates here on $el (this user card)
}, val);
/*setTimeout(function(){
// Do something else when val ms is close to completion
// here on $el (this user card)
}, 0.9 * val);*/
})
If you want multiple things to happen (change bg color and then, later, hide element, for example) you can set multiple setTimeouts to happen with different time values derived from val
you want to add a function with setTimeout() for ajax success: parameter.
Ex(with jquery):-
$.ajax({
// your ajax process
success:function(){
setTimeout(function(){
$('.card-w').not('.anotherclassname').addClass('someclassname-'+i);
$('someclassname-'+i).addClass('.anotherclassname').fadeOut();
},6000);
}
})
The accepted answer can give you a lot of trouble if the ajax part
however_you_create_your_element;
attach cardElement to the DOM
Is replacing or adding elements. Ajax and processCards share cardlist and your ajax may remove items from DOM but leave them in cardlist.
You failed to mention if you replace the card list in your ajax or append new cards but the following solution would work either way.
To adjust to updating every minute and showing minutes you can change the following 2 lines:
const repeat = 1000;//repeat every second
const message = timePassed=>`${Math.round(timePassed/1000)} seconds`
to:
const repeat = 60000;//repeat every minute
const message = timePassed=>`${Math.round(timePassed/60000)} min`
(function(){//assuming cardContainer is available
const container = document.querySelector("#cardContainer");
const repeat = 1000;//repeat every second
const message = timePassed=>`${Math.round(timePassed/1000)} seconds`
const updateCards = function(){
Array.from(container.querySelectorAll(".user-w .user-date"))
.map(
(element)=>{
var started = element.getAttribute("x-started");
if(started===null){
started = Date.now()-
parseInt(element.innerText.trim().replace(/[^0-9]/g,""),10)*60000;
element.setAttribute("x-started",started);
}
return [
element,
Date.now()-parseInt(started,10)
];
}
).forEach(
([element,timePassed])=>
element.innerText = message(timePassed)
);
}
setInterval(updateCards,repeat);
}());
<div id="cardContainer">
<div class="user-w">
<div class="avatar with-status status-green">
<img alt="" src="img/avatar1.jpg">
</div>
<div class="user-info">
<div class="user-date">
12 min
</div>
<div class="user-name">
John Mayers
</div>
<div class="last-message">
What is going on, are we...
</div>
</div>
</div>
</div>

show hide on logic js

I have some logic
$("#finish-button").click(function () {
$(".hidebuttonvresults").hide();
$(".showbuttonvresults").show();
});
This works for most of my pages, however one page does not have #finish-button. The only unique is id is #question-number but I only want this action to happen when the ID prints Question 15 of 15.
Any idea how to alter my logic to include this extra factor? I was trying
var str = $('#question-number').html();
if (str.indexOf("Question 40 of 46") !== -1) {
$(".hidebuttonvresults").hide();
$(".showbuttonvresults").show();
}
but it does not work

Performance with jQuery .nextUntil on large DOM

I'm looking to address a performance issue I'm having with a very large DOM. In essence, this a word-processing style app inside the browser using contenteditable divs.
Suppose I have a structure like this:
<div class="header">...</div>
<div class="actor">...</div>
<div class="director">...</div>
<div class="producer">...</div>
<div class="writer">...</div>
<div class="executive">...</div>
<div class="studio">...</div>
<div class="footer">...</div>
I then have some code which ends up returning (for example):
<div class="writer">...</div>
as a jQuery object. I then need to retrieve all of this object's surrounding divs between header and footer as a selection and then further filter this list using a class e.g. 'actor'.
Currently, I have the following code, which works correctly:
// Find header
var header_object = object.prevUntil(".header").last().prev();
// Select all objects between header and footer, and then filter
var object_list = header_object.nextUntil(".footer", ".actor");
// Iterate through object_list
object_list.each(function()
{
// Run additional code on the objects
});
The only problem is that due to the app being a word processor of sorts, the DOM structure is often very large (e.g. over 5000 elements) and executing this code locks up the browser for an unacceptable amount of time (over 10 - 30 seconds).
As such, I'm looking for a way to customize the code I have to make it more efficient / improve performance.
I should also point out that the HTML structure above is not (header - 5000 elements - footer), rather it is 200 x (header - elements - footer). As such, each traversal operation is only maybe 25 elements from header to footer, but it has to run many times.
Any suggestions? Many thanks!
You could enhance performance by not using jQuery, and creating your own functions that are more specific to your use case.
function getClosest(el, klass, dir) {
while (el && (!el.classList.contains(klass))) {
el = el[dir ? 'previousElementSibling' : 'nextElementSibling'];
}
return el;
}
function getbetween(from, to, filterKlass) {
var list = [];
while(from && to && from !== to) {
if ((from = from.nextElementSibling) !== to) {
filterKlass ? (from.classList.contains(filterKlass) ? list.push(from) : Infinity) : list.push(from);
}
}
return list;
}
var object = $('.writer');
var element = object.get(0);
var header_object = getClosest(element, 'header', true);
var footer_object = getClosest(element, 'footer', false);
var object_list = getbetween(header_object, footer_object, 'actor');
object_list.forEach(function(element) {
console.log(element);
});
FIDDLE
Traversing the next element sibling directly, and checking for classes, should be much faster than using nextUntil

Simplify my menu animation code

I've got a bunch of 'project' divs that I want to expand when they're clicked on. If there's already a project open, I want to hide it before I slide out the new one. I also want to stop clicks on an already open project from closing and then opening it again.
Here's an example of what I mean (warning - wrote the code in the browser):
$('.projects').click(function() {
var clicked_project = $(this);
if (clicked_project.is(':visible')) {
clicked_project.height(10).slideUp();
return;
}
var visible_projects = $('.projects:visible');
if (visible_projects.size() > 0) {
visible_projects.height(10).slideUp(function() {
clicked_project.slideDown();
});
} else {
clicked_project.slideDown();
}
});
Really, my big issue is with the second part - it sucks that I have to use that if/else - I should just be able to make the callback run instantly if there aren't any visible_projects.
I would think this would be a pretty common task, and I'm sure there's a simplification I'm missing. Any suggestions appreciated!
slideToggle?
$('.projects').click(function() {
var siblings = $(this).siblings('.projects:visible');
siblings.slideUp(400);
$(this).delay(siblings.length ? 400 : 0).slideToggle();
});
Used a delay rather than a callback because the callback is called once per matched item. This would lead to multiple toggles if multiple items were visible.
Like this?
$(".projects")
.click(function () {
var a = $(this);
if (a.is(":visible")) return a.height(10)
.slideUp(), void 0;
var b = $(".projects:visible");
b.size() > 0 ? b.height(10)
.slideUp(function () {
a.slideDown()
}) : a.slideDown()
})

Categories

Resources