I have a javascript function that filter dom elements based on a input text changes, so:
$(".input").keyup(function(e) {
filter();
});
var cache = $(".dom");
var filter = function() {
cache.each(function() {
// if field contains some text show else hide
}
};
My problem happens when there are many dom elements to filter, the whole pages gets inaccessible because of the synchronous processing (like the example above). Im trying to come out with a solution that dont locks the entire page with synchronous processing.
The problem is NOT related to the filter logic (it's completely trivial), it's NOT related to the jquery or javascript itslef, it's related to the synchronous processing and the quantity of dom elements.
As JavaScript is single threaded, the only real way to sort this out is to split the long-running job into a series of shorter jobs, and use setTimeout() with a short time delay at the end of each section to kick off the next one. This gives your UI and other JavaScript events a chance to update.
You can update large set of dom nodes by placing them in a queue and process only a few elements on each setTimeout "tick". In pseudo-code:
on(event):
queue = DOM nodes to be updated
setTimeout(update)
update:
queue.slice(0, limit).each(...update a node...)
queue = queue.slice(limit) // remove processed nodes
if (queue.length > 0) setTimeout(update) // repeat...
See http://jsfiddle.net/Etsmm/2/ for a complete working example.
Upd: The first version had problems with Chrome (related to its' "display" bug), adding a fix as per this answer appears to have done the trick.
Or doing it elsewhere through an ajax request if's really too long ?
And, maybe, some kind of: first step, select all IDs to be hidden in an array, then, settimeout, then in a second step, hiding them like 50 per 50 ?
Also, maybe processing that having the container of all those elements himself hidden, and then, once done, reshowing it may be faster ?
For this kind of purposes I generally prefer Ben Alman's message queuing library. It has also different alternatives. This one is quite successful on throttling.
https://github.com/cowboy/jquery-message-queuing/
Here below a throttling sample
http://benalman.com/code/projects/jquery-message-queuing/examples/throttling/
thank you all for your help. I came up with a solution based on Ben Clayton response, but Im still looking for ideas and investigating the thg435 solution. Any comments will be apreciated.
<script type="text/javascript">
$(document).ready(function () {
var cache = $(".whatever");
var wait = 0;
var input = $("#input");
var regex = null;
input.keyup(function (e) {
go.index = 0;
clearTimeout(wait);
wait = setTimeout(go.start, 500);
});
var filter = function (i) {
var one = cache.eq(i - 1);
one.text().match(regex) ? one.show() : one.hide();
go.index++;
setTimeout(go.filter, 10);
};
go = {
index: 0,
filter: function () {
go.index == 0 || go.index > cache.length ? null : filter(go.index);
},
start: function () {
go.index = 1;
var search = input.val();
search = search.replace(new RegExp("[a]", "gi"), "[aàáâã]");
search = search.replace(new RegExp("[e]", "gi"), "[eéê]");
search = search.replace(new RegExp("[i]", "gi"), "[ií]");
search = search.replace(new RegExp("[o]", "gi"), "[oóô]");
search = search.replace(new RegExp("[u]", "gi"), "[uú]");
search = search.replace(new RegExp("[c]", "gi"), "[cç]");
regex = new RegExp(search, "gi");
go.filter();
}
}
});
</script>
<input type="text" id="input" />
<span class="whatever">lalala</span>
<span class="whatever">leléLÉ</span>
<span class="whatever">lololo</span>
<span class="whatever">lululu</span>
Related
So I have so an image will show when certain text is moused over, it will delay when the mouse is taken off before disappearing and that works just fine. But I have multiple images that can show up depending on the text hovered, I want to have the first image skip it's delay if other text is moused over so it doesn't expand the page and stack the images. Is this possible? Or will I just need to pull the delay off? This is done within JQuery
$("h2").mouseover(function ()
{
let mouseText = $(this).attr("class").split(" ")[0];
switch(mouseText)
{
case "wh-light":
imgID += mouseText;
break;
case "wh-hl-ll":
imgID += mouseText;
break;
case "part-hh-ll":
imgID += mouseText;
break;
}
$(imgID).show();
});
$("h2").mouseout(function ()
{
$(imgID).delay(1000).hide(0);
});
I can supply the HTML but I don't think it is too relevant to this. Thank you in advance for your help!
Without having your template and complete code, I'd have to contrive a full example... but the below snippet should give you a good example of what you'll have to do. You'll need to keep track of the current image and your callback has to know what the current image was when the delay was kicked off and compare it to the current to know whether it should do anything or not.
For this reason I pulled the code into separate javascript functions and used setTimeout instead of the jquery delay(). The documentation on the actual jquery site even mentions that the delay function is limited and has no way to cancel itself and as such should not be treated as a replacement for setTimeout().
Ref https://api.jquery.com/delay/
The .delay() method is best for delaying between queued jQuery effects. Because it is limited—it doesn't, for example, offer a way to cancel the delay—.delay() is not a replacement for JavaScript's native setTimeout function, which may be more appropriate for certain use cases.
Without knowing how you were building the imgID variable or clearing it out... I added the baseImageID and targetImageID variables so that you don't end up just continuously concatenating the new strings onto the end of imgID...
As a last note... I like to make things very modular and verbose when talking through a solution so please dont hate on the extra functions... I think they make the solution easier to understand. The actual implementation can be streamlined...
let baseImageID = 'image-';
let targetImageID = '';
let currentImageID = '';
const hideImageNow = function(imageID) {
$(imageID).hide();
if (currentImageID == imageID) {
currentImageID = '';
}
};
const hideImageLater = function(imageID, delay) {
setTimeout(hideImageNow, delay, imageID);
};
const showImage = function(imageID) {
if (currentImageID != '') {
hideImageNow(currentImageID);
}
currentImageID = imageID;
$(imageID).show();
};
$("h2").mouseover(function() {
targetImageID = baseImageID;
let mouseText = $(this).attr("class").split(" ")[0];
switch (mouseText) {
case "wh-light":
targetImageID += mouseText;
break;
case "wh-hl-ll":
targetImageID += mouseText;
break;
case "part-hh-ll":
targetImageID += mouseText;
break;
}
showImage(targetImageID);
});
$("h2").mouseout(function() {
hideImageLater(currentImageID, 1000);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
So I am working on a personal userscript for myself and a few friends. The userscript is used to block keywords on a page.
To search for the keywords on the page I use:
var filter = ["worda", "wordb", "wordc"];
var found = false;
var check =
function () {
//Stores the content of the head and body in the document.
var head = $("head").html();
var body = $("body").html();
$.each(filter, function(index, item) {
if ((head + body).toString().toLowerCase().contains(item.toLowerCase())) {
window.clearInterval(interval);
console.log("Found: " + item);
found = true;
return false;
}
});
if (found) {
$("body").hide();
var originalTitle = $(document).prop("title");
$(document).prop("title", "Blocked");
window.setTimeout(function() {
if (confirm("This page contains a blocked keyword, are you sure you'd like to continue?")) {
$(document).prop("title", originalTitle);
$("body").show();
return;
}
}, 1);
}
};
And then I set it to repeat every second through:
var interval = window.setInterval(check, 1000);
The reason I re-check the page every second is because new content may have been dynamically created through Javascript, and I need to make sure that it is also filtered.
However, I'd like to know if this is the most efficient way to scan the page for keywords, or if there is a more efficient method which does not require me to re-check the entire page (possibly only need to re-check new elements).
Also I'd like to push the interval as low as I can (I'm thinking around 100ms), however I do not know if this will be very resource-friendly.
Thank you for the help.
You're correct that scanning the content of the page periodically is an inefficient approach to the problem. That being said, if it's only running once a second, it probably isn't a huge deal.
Still, there's a better option. The MutationObserver interface can be used to fire events when a document, or a part of a document, is modified. This is perfect for what you're trying to do:
var observer = new MutationObserver(function() {
… scan the document for your keywords …
});
observer.observe(document, {
childList: true,
characterData: true,
subtree: true
});
I have a table with games detail. and I want to add a countdowntimer for every game.
I'm using the jquery-countdownTimer plugin
My Html:
<span class="given_date" data-gamestart="2016/3/11 15:30"></span>
<span class="given_date" data-gamestart="2016/3/13 18:00"></span>
<span class="given_date" data-gamestart="2016/3/15 17:45"></span>
<span class="given_date" data-gamestart="2016/3/22 19:45"></span>
and i user a this plugin to make the count down.
My js:
$('.given_date').countdowntimer({
dateAndTime : "2016/3/10 17:05"
});
A jsFiddle example
My issue is, even though I provide a different time for each span, all of the timers show the same value and countdown in sync
Why does each span not show it's own timer based on my provided values?
Your issue is that your elements do not have ids.
This plugin uses the below code to set the timers then display their values. Note the window['regexpMatchFormat_' + $this.attr('id')]. This uses the id of each element to create a unique global variable to keep track of the timers. If your elements have no ids, you end up repeatedly overwriting your first timer
//Code for starting the timers.
if (options.regexpMatchFormat != undefined && options.regexpReplaceWith != undefined && options.timeSeparator == undefined) {
window['regexpMatchFormat_' + $this.attr('id')] = options.regexpMatchFormat;
window['regexpReplaceWith_' + $this.attr('id')] = options.regexpReplaceWith;
}
//Function for displaying the timer.
function html($this, content) {
var processedContent = content;
if (typeof window['regexpMatchFormat_' + $this.attr('id')] !== 'undefined' &&
typeof window['regexpReplaceWith_' + $this.attr('id')] !== 'undefined') {
var regexp = new RegExp(window['regexpMatchFormat_' + $this.attr('id')]);
processedContent = content.replace(regexp,
window['regexpReplaceWith_' + $this.attr('id')]);
}
$this.html(processedContent);
}
Working jsFiddle
This is why you should always link to the plugin you are using ;)
The best way is to loop through the spans with jQuery's .each() and set up the countdowntimer within that loop. Something like this:
$('.given_date').each(function() {
$(this).countdowntimer({
dateAndTime : this.getAttribute("data-gamestart")
});
});
All,
I have a 'credit module' (similar to credit system in games), which when a user performs an action, creates an inner div with the cost to be added or substracted so user can see what the cost of the last action was.
Problem: Everything works fine as long as the function is called once, if the user performs multiple actions quickly, the setTimeout functions (which are suppose to animate & then delete the cost div) donot get executed. It seems the second instance of the function resets the setTimeout function of the first.
(function()
{
$("#press").on("click", function(){creditCost(50)});
function creditCost(x)
{
var eParent = document.getElementById("creditModule");
// following code creates the div with the cost
eParent.innerHTML += '<div class="cCCost"><p class="cCostNo"></p></div>';
var aCostNo = document.getElementsByClassName("cCostNo");
var eLatestCost = aCostNo[aCostNo.length - 1];
// following line assigns variable to above created div '.cCCost'
var eCCost = eLatestCost.parentNode;
// cost being assigned
eLatestCost.innerHTML = x;
$(eCCost).animate ({"left":"-=50px", "opacity":"1"}, 250, "swing");
// following code needs review... not executing if action is performed multiple times quickly
setTimeout(function()
{
$(eCCost).animate ({"left":"+=50px", "opacity":"0"}, 250, "swing", function ()
{
$(eCCost).remove();
})
}, 1000);
}
})();
jsfiddle, excuse the CSS
eParent.innerHTML += '<div class="cCCost"><p class="cCostNo"></p></div>';
is the bad line. This resets the innerHTML of your element, recreating the whole DOM and destroying the elements which were referenced in the previous invocations - letting their timeouts fail. See "innerHTML += ..." vs "appendChild(txtNode)" for details. Why don't you use jQuery when you have it available?
function creditCost(x) {
var eParent = $("#creditModule");
// Create a DOM node on the fly - without any innerHTML
var eCCost = $('<div class="cCCost"><p class="cCostNo"></p></div>');
eCCost.find("p").text(x); // don't set the HTML if you only want text
eParent.append(eCCost); // don't throw over all the other children
eCCost.animate ({"left":"-=50px", "opacity":"1"}, 250, "swing")
.delay(1000) // of course the setTimeout would have worked as well
.animate ({"left":"+=50px", "opacity":"0"}, 250, "swing", function() {
eCCost.remove();
});
}
You are starting an animation and scheduling a timeout to work on DOM elements that will get modified in the middle of that operation if the user clicks quickly. You have two options for fixing this:
Make the adding of new items upon a second click to be safe so that it doesn't mess up the previous animations.
Stop the previous animations and clean them up before starting a new one.
You can implement either behavior with the following rewrite and simplification of your code. You control whether you get behavior #1 or #2 by whether you include the first line of code or not.
function creditCost(x) {
// This first line of code is optional depending upon what you want to happen when the
// user clicks rapid fire. With this line in place, any previous animations will
// be stopped and their objects will be removed immediately
// Without this line of code, previous objects will continue to animate and will then
// clean remove themselves when the animation is done
$("#creditModule .cCCost").stop(true, false).remove();
// create HTML objects for cCCost
var cCCost = $('<div class="cCCost"><p class="cCostNo">' + x + '</p></div>');
// add these objects onto end of creditModule
$("#creditModule").append(cCCost);
cCCost
.animate ({"left":"-=50px", "opacity":"1"}, 250, "swing")
.delay(750)
.animate({"left":"+=50px", "opacity":"0"}, 250, "swing", function () {
cCCost.remove();
});
}
})();
Note, I changed from setTimeout() to .delay() to make it easier to stop all future actions. If you stayed with setTimeout(), then you would need to save the timerID returned from that so that you could call clearTimeout(). Using .delay(), jQuery does this for us.
Updated code for anyone who might want to do with mostly javascript. Jsfiddle, excuse the CSS.
function creditCost(x)
{
var eParent = document.getElementById("creditModule");
var eCCost = document.createElement("div");
var eCostNo = document.createElement("p");
var sCostNoTxt = document.createTextNode(x);
eCCost.setAttribute("class","cCCost");
eCostNo.setAttribute("class","cCostNo");
eCostNo.appendChild(sCostNoTxt);
eCCost.appendChild(eCostNo);
eParent.insertBefore(eCCost, document.getElementById("creditSystem").nextSibling);
$(eCCost).animate ({"left":"-=50px", "opacity":"1"}, 250, "swing");
setTimeout(function()
{
$(eCCost).animate ({"left":"+=50px", "opacity":"0"}, 250, "swing", function ()
{
$(eCCost).remove();
})
}, 1000);
}
I have been looking around and I cannot seem to figure out how to do this, although it seems like it would be very simple.(mobile development)
What I am trying to do is display a message (kind of like an alert, but not an alert, more like a dialog) while a calculation is being made. Simply like a Loading please wait. I want the message to appear and stay there while the calculation is being done and then be removed. I just cannot seem to find a proper way of doing this.
The submit button is pressed and first checks to make sure all the forms are filled out then it should show the message, it does the calculation, then hides the message.
Here is the Calculation function.
function scpdResults(form) {
//call all of the "choice" functions here
//otherwise, when the page is refreshed, the pulldown might not match the variable
//this shouldn't be a problem, but this is the defensive way to code it
choiceVoltage(form);
choiceMotorRatingVal(form);
getMotorRatingType();
getProduct();
getConnection();
getDisconnect();
getDisclaimer();
getMotorType();
//restore these fields to their default values every time submit is clicked
//this puts the results table into a known state
//it is also used in error checking in the populateResults function
document.getElementById('results').innerHTML = "Results:";
document.getElementById('fuse_cb_sel').innerHTML = "Fuse/CB 1:";
document.getElementById('fuse_cb_sel_2').innerHTML = "Fuse/CB 2:";
document.getElementById('fuse_cb_result').innerHTML = "(result1)";
document.getElementById('fuse_cb_res_2').innerHTML = "(result2)";
document.getElementById('sccr_2').innerHTML = "<b>Fault Rating:</b>";
document.getElementById('sccr_result').innerHTML = "(result)";
document.getElementById('sccr_result_2').innerHTML = "(result)";
document.getElementById('contactor_result').innerHTML = "(result)";
document.getElementById('controller_result').innerHTML = "(result)";
//Make sure something has been selected for each variable
if (product === "Choose an Option." || product === "") {
alert("You must select a value for every field. Select a Value for Product");
**************BLAH************
} else {
//valid entries, so jump to results table
document.location.href = '#results_a';
******This is where the message should start being displayed***********
document.getElementById('motor_result').innerHTML = motorRatingVal + " " + motorRatingType;
document.getElementById('voltage_res_2').innerHTML = voltage + " V";
document.getElementById('product_res_2').innerHTML = product;
document.getElementById('connection_res_2').innerHTML = connection;
document.getElementById('disconnect_res_2').innerHTML = disconnect;
if (BLAH) {
}
else {
}
populateResults();
document.getElementById('CalculatedResults').style.display = "block";
} //end massive else statement that ensures all fields have values
*****Close out of the Loading message********
} //scpd results
Thank you all for your time, it is greatly appreciated
It is a good idea to separate your display code from the calculation code. It should roughly look like this
displayDialog();
makeCalculation();
closeDialog();
If you are having trouble with any of those steps, please add it to your question.
Computers are fast. Really fast. Most modern computers can do several billion instructions per second. Therefore, I'm fairly certain you can rely on a a setTimeout function to fire around 1000ms to be sufficient to show a loading message.
if (product === "Choose an Option." || product === "") {
/* ... */
} else {
/* ... */
var loader = document.getElementById('loader');
loader.style.display = 'block';
window.setTimeout(function() {
loader.style.display = 'none';
document.getElementById('CalculatedResults').style.display = "block";
}, 1000);
}
<div id="loader" style="display: none;">Please wait while we calculate.</div>
You need to give the UI main thread a chance to render your message before starting your calculation.
This is often done like this:
showMessage();
setTimeout(function() {
doCalculation();
cleanUp()
}, 0);
Using the timer allows the code to fall through into the event loop, update the UI, and then start up the calculation.
You're already using a section to pop up a "results" page -- why not pop up a "calculating" page?
Really, there are 4,000,000 different ways of tackling this problem, but why not try writing a "displayCalculatingMessage" function and a "removeCalculatingMessage" function, if you don't want to get all object-oriented on such a simple thing.
function displayCalculatingMessage () {
var submit_button = getSubmitButton();
submit_button.disabled = true;
// optionally get all inputs and disable those, as well
// now, you can either do something like pop up another hidden div,
// that has the loading message in it...
// or you could do something like:
var loading_span = document.createElement("span");
loading_span.id = "loading-message";
loading_span.innerText = "working...";
submit_button.parentElement.replaceChild(loading_span, submit_button);
}
function removeCalculatingMessage () {
var submit_button = getSubmitButton(),
loading_span = document.getElementById("loading-message");
submit_button.disabled = false;
loading_span.parentElement.replaceChild(submit_button, loading_span);
// and then reenable any other disabled elements, et cetera.
// then bring up your results div...
// ...or bring up your results div and do this after
}
There are a billion ways of accomplishing this, it all comes down to how you want it to appear to the user -- WHAT you want to have happen.