I've got a long running method -- and I want to indicate to the user that an operation is underway. This is NOT and ajax call, so I can't use the common pattern I've used in the past to display, say a spinner, before the ajax event, then hiding it on success for example.
In my case -- I'm not making an ajax call, I'm instead doing some very heavy DOM manipulation.
I had a test example on jsFiddle.net -- would love to learn how to capture the event. At the moment, my "wait-message" div updates at the same exact time when my operation completes which is much too late :(
Complete sample code is here: http://jsfiddle.net/rsturim/97hrs/6/
Javascript (jQuery)
$(document).ready(function() {
$("#link-action").click(function(e) {
$("#wait-message").text("starting ...");
var count = longRunningMethod(1000000000);
$("#result").text(count);
$("#wait-message").text("completed.");
});
var longRunningMethod = function(countUpTo) {
var i = 0;
while (i <= countUpTo) {
i++;
}
return i;
};
});
HTML:
<div id="wait-message">
push button please
</div>
<hr />
<button id="link-action">Run Operation</button>
<hr />
<h1>Results:</h1>
<div id="result"> </div>
Here is a solution. I'm not sure if it works in all browsers, you may want to test it out in several, but I think it does:
$(document).ready(function() {
$("#link-action").click(function(e) {
$("#wait-message").text("starting ...");
// Stuff to do after a render
setTimeout(function(){
var count = longRunningMethod(1000000000);
$("#result").text(count);
$("#wait-message").text("completed.");
}, 0);
});
var longRunningMethod = function(countUpTo) {
var i = 0;
while (i <= countUpTo) {
i++;
}
return i;
};
});
Basically, the browser won't render any changes until a script finishes executing. That allows you to do things like:
Hide all divs with a certain class
Show one of those divs
In a row and the browser will never render the div that is being shown as hidden, so you won't get weird flickers or things moving around on the page.
Using setTimeout like I did, the anonymous click handler will finish executing, the browser will re-render, the the anonymous function in the setTimeout will run (immediately after the render since there is no actual delay).
Use setTimeout or setInterval instead of your while loop; a sub-second delay like 15ms should be enough to prevent your window freezing / UI locking.
Related
I have an endless scrolling made by ajax and jQuery. It's using the items paginated in Controller (Laravel5), getting it with ajax.
Everything works fine, however I have a problem. When I hit the bottom of the page, it makes an ajax call twice. I suspect it's because of the setInterval because when I change the time, it affects exactly that part.
// Creates the pagination pages [<[1]2]3]>]
{!! $boxes->render() !!}
// Html
<input type="hidden" id="page" value="1" />
<input type="hidden" id="max_page" value="{{{ $boxes-id }}}" />
<div id="end_of_page" class="center">
<hr/>
<span>End of the feed.</span>
<script>
$(document).ready(function() {
var didScroll = false;
$(window).scroll(function() { //watches scroll of the window
didScroll = true;
});
//Sets an interval so your window.scroll event doesn't fire constantly. This waits for the user to stop scrolling for not even a second and then fires the pageCountUpdate function (and then the getPost function)
setInterval(function() {
if (didScroll){
didScroll = false;
if(($(document).height()-$(window).height())-$(window).scrollTop() < 10){
pageCountUpdate();
}
}
}, 250);
When I change 250 to 5000 for example, it gives a time in between, but this is not what I want to make. I want to make the call only one time once I hit the bottom, maybe disable it with a boolean variable, and then reactivate it after new elements load (on ajax success state), but I couldn't figure it out where to put the blocking variable.
//Continuing
function pageCountUpdate(){
var page = parseInt($('#page').val());
var max_page = parseInt($('#max_page').val());
if(page < max_page){
$('#page').val(page+1);
getPosts();
$('#end_of_page').hide();
} else {
$('#end_of_page').fadeIn();
}
}
function getPosts(){
var data = { "page": $('#page').val() }
$.ajax({
type: "POST",
//Classic ajax call
beforeSend: function(){
..}
complete: function(){
..}
success: function(){
..}
});
}
}
});
Update: Regarding to John Resig's example mentioned in comments, I need to use var outerPane = $details.find(".details-pane-outer"),
didScroll = false;. I think not having this part creates my problem, but I couldn't figure out where to choose with find() method.
apart from your weird setinterval structure (just move the code into the scroll event, without an interval).
It seems that the scroll event is called multiple times inside the last 10 pixels of the bottom of the page. So while scrolling to the bottom, at 9 px away form the bottom, the event is fired which loads more posts. But even before the new posts are loaded, in the meanwhile you scroll a little further which again fires the scroll event. This makes your posts load twice or even more than twice.
To solve this you can add a simple boolean switch that makes sure that the posts don't get loaded again when they're already being loaded. Something like:
var loading=false;
function onReachScrollLimit(){
if(loading){return;}
loading=true;
load_new_posts();
}
function load_new_posts(){
//insert the posts
loading=false;
}
https://jsfiddle.net/1vm0259x/ I want to have it so when the contents of #b changes it immediately makes the div display. Is that possible? I'm having to get a little hacky because of the limitations of a CMS plugin. I don't know jQuery very well.
Markup
<div id="a">
<span id="b">0 items</span>
</div>
jQuery
$(document).ready(function(){
if($('#b:contains("0 items")')) {
$('#a').css("display", "none");
}
if($('#b:not(:contains("0 items"))')) {
$('#a').css("display", "block");
}
});
The best way to monitor for text changes like this are to find a way to hook into some related and existing event in the browser and then see if the text has changed to what you want.
In the newer browsers (such as IE11+, recent versions of Chrome, Firefox and Safari), you can use a DOM MutationObserver to directly watch to see if the text nodes change.
The mutation callback is called anytime the children of the specified element are changed (children include the text nodes).
Here's some runnable code that watches for a text change in a div in this code snippet:
document.getElementById("go").addEventListener("click", function(e) {
var t = document.getElementById("test");
t.innerHTML = parseInt(t.innerHTML, 10) + 1;
});
var m = new MutationObserver(function(mRecords, obj) {
log("Current text value: " + document.getElementById("test").innerHTML);
}).observe(document.getElementById("test"), {childList: true, characterData: true, subtree: true});
function log(x) {
var d = document.createElement("div");
d.innerHTML = x;
document.body.appendChild(d);
}
<button id="go">Click Me to Change the Text</button><br><br>
<div id="test">1</div><br><br>
If you need support for older version of IE, then the next best thing would be to figure out what existing events in the browser precede the text change and monitor those events. When one of those events occurs, you can then check the text. For example, if the action that triggers the text change always comes after an Ajax call, you can monitor/hook Ajax calls on a system wide basis and then check your text after each Ajax call completes. Since this only ever does anything when other things are already happening in the web page, it's very efficient. Or, if the text only changes after a particular button is clicked or some text field is changed, you can monitor those DOM elements with event listeners.
To suggest how to do that more specifically, we'd need to see the details of your actual circumstance and would have to understand what events in the page lead to the changing text. Such short duration timers can also negatively affect the performance of things like animations running in your page or in other tabs.
It is NOT recommended to use a short duration timer to poll the DOM because this kills mobile battery life and, in fact, mobile browsers will attempt to delay or slow down any long running interval timers you use in order to try to preserve battery life.
On top of MutationObserver, eloquently put by #jfriend00, there is an older API available at our disposal as well by the name DOMSubtreeModified. Combine that with onpropertychange that of Internet Explorer and I believe you get a nice backward compatible change event. Take a look at the snippet below, not thoroughly tested though:
Snippet:
var myDIV=document.getElementById('a');
var mySpan=document.getElementById('b');
var myButton=document.getElementById('button');
var myResult=document.getElementById('result');
if(window.addEventListener){
myButton.addEventListener('click',onButtonClicked,false);
mySpan.addEventListener('DOMSubtreeModified',onSpanModified,false);
}else{
myButton.attachEvent('onclick',onButtonClicked);
mySpan.attachEvent('onpropertychange',onSpanModified);
}
function onButtonClicked(){
//mySpan.innerText=Math.random();
mySpan.innerHTML=Math.random();
}
function onSpanModified(){
myResult.innerHTML=mySpan.innerHTML;
}
<div id="a">
<span id="b">0</span>
</div>
<input id="button" type="button" value="Click Me" />
<span id="result"></span>
Hope this helps in some way though. Apologies if this was not what you were looking for and if I misunderstood your problem completely.
You can't really "watch" something in JavaScript since it's an event-driven language (there are a few exceptions to this rule when it comes to object properties, but it is not something that is commonly useful).
Instead you should set an interval that updates regularly. I used 50 millisecond intervals, you can choose to use whatever interval you like:
function update() {
if($('#b:contains("0 items")').length) {
$('#a').css("display", "none");
}
if($('#b:not(:contains("0 items"))').length) {
$('#a').css("display", "block");
}
}
$(document).ready(function () {
setInterval(update, 50);
});
I have few filters that show or hide table rows in a table and then checks if there are no rows visible then add a message to the table saying no row exists.
This is how I am trying to do it, but fails miserably, maybe I am not aware of with the way JS executes lines, maybe asynchronous always ?
HTML
<input ID="cbFreshFruits" Checked="true" onclick="ToggleFruits(this)" />
Script
function ToggleFruits(elm) {
$target = $('#fruitTable tr[data-status="' + elm.id + '"]').filter(function () {
var crates = $("#crateTable tr .selected").parent().map(function () {
return $(this).data("crateID");
}).get();
return crates.indexOf($(this).data("crateID")) >= 0;
});
if (elm.checked) {
$target.show("down");
}
else {
$('#fruitTable tr[data-status="' + elm.id + '"]').hide("up");
}
CheckFruitExists();
}
function CheckFruitExists() {
$("#NoFilteredFruits").hide();
$("#fruitTable").show();
if ($("#fruitTable tr:visible").length < 1) {
$("#NoFilteredFruits").show();
$("#fruitTable").hide();
}
}
Problem
When checked in developer tools, JS executes this statement
if ($("#fruitTable tr:visible").length < 1)
before this one,
else {
$('#fruitTable tr[data-status="' + elm.id + '"]').hide("up");
}
I mean it executes it, but it changes are not made, e.g. it doesn't hide any rows before excuting if statement.
Please note, I am using CheckFruitExists at different places, so I would like to keep it separate.
JavaScript and the manipulation of the DOM are single threaded. Browsers work by placing items in a queue and executing them one after the other. When you're updating the DOM, what's actually happening is it's putting the request to update it at the end of the queue, so it will happen after your JavaScript has run to completion. What you need to do in this case is force your second function to go to the end of this queue, so it can run after the DOM manipulation has happened. You can do this by wrapping the call to the function in a zero-timeout:
window.setTimeout(CheckFruitExists, 0);
This will cause it to get added to the end of the execution queue and should now run after the DOM updates have happened.
Update:
As per my comment, the jQuery visible selector considers elements that are in an animation cycle:
During animations that hide an element, the element is considered visible until the end of the animation. During animations to show an element, the element is considered visible at the start at the animation.
This means that when your CheckFruitExists function runs, the element will still be animating and will be considered visible, regardless of whether it's hiding or showing. Try removing your animations to see if this fixes the issue (you will probably still need to use the setTimeout fix as above). If it does, and you still wish to use the animations, you will need to call CheckFruitExists in the animation complete callback function of hide and show instead.
I want to replay my jquery function ChangeStats() every 5 seconds, it's currently doing sod all.
function ChangeStats() {
$('body').find('.admin-stats-big-figures-hidden').fadeIn(500);
setTimeout(function() {
$('body').find('.admin-stats-big-figures').fadeOut(500);
}, 500);
}
$(document).ready(function(){
setInterval(ChangeStats, 5000);
})();
Yes I have got the right class names.
No I haven't used underscores in my HTML.
I think it's something to do with my use of "find()", once the DOM has loaded and the function is set is it meant to traverse up the DOM tree instead of down?
EDIT:
Updated code, still not working.
HTML:
<span class="admin-stats-big-figures">%productCount%</span>
<span class="admin-stats-big-figures-hidden">hey</span>
Ok, I am going to go out on a limb and make several assumptions here; one is that you wish to cycle between two elements repeatedly, another is that you are using $(this) in the context of the window rather than a containing element. If either of these are incorrect then the following solution may not be suitable. However, let's give this a shot, eh?
1) You need to use setInterval rather than setTimeout to create a repeating call. You can of course "chain" your timeouts (ie: call the succeeding timeout from the code of the current timeout). This has some benefits in certain situations, but for now let's just assume you will use intervals rather than timeouts.
2) You call the find() jQuery method every time, which is a little unnecessary, especially if you will be repeating the actions so one idea would be to cache the lookup. If you are going to do that a custom object would be more suitable than separate global variables.
3) Some flexibility in terms of starting and stopping the animation could be provided. If we use a custom object as mentioned in (2) then that can easily be added.
4) You are using fadeIn and fadeOut, however if you wish the items to cycle then fadeToggle may be your best solution as it will simply allow you to do exactly that, toggle, without needing to check the current opacity state of the element.
5) Finally in my example I have provided a little extra "padding HTML" in order for the example to look good when run. Fading in jQuery will actually set the faded item to a CSS display of "none" which results in the content "jumping about" in this demo, so I have used some div's and a couple of HTML entity spaces to keep the formatting.
Ok, after all that here is the code..
// your custom animation object
var myAnim = {
// these will be cached variables used in the animation
elements : null,
interval : null,
// default values for fading and anim delays are set to allow them to be optional
delay : { fade: 500, anim: 200 },
// call the init() function in order to set the variables and trigger the animation
init : function(classNameOne, classNameTwo, fadeDelay, animDelay) {
this.elements = [$("."+classNameOne),$("."+classNameTwo)];
// if no fade and animation delays are provided (or if they are 0) the default ones are used
if (animDelay) this.delay.anim = animDelay;
if (fadeDelay) this.delay.fade= fadeDelay;
this.elements[0].fadeOut(function(){myAnim.start()});
},
// this is where the actual toggling happens, it uses the fadeToggle callback function to fade in/out one element once the previous fade has completed
update : function() {
this.elements[0].fadeToggle(this.delay.anim,function(el,delay){el.fadeToggle(delay)}(this.elements[1],this.delay.anim));
},
// the start() method allows you to (re)start the animation
start : function() {
if (this.interval) return; // do nothing if the animation is currently running
this.interval = setInterval(function(){myAnim.update()},this.delay.fade);
},
// and as you would expect the stop() stops it.
stop : function () {
if (!this.interval) return; // do nothing if the animation had already stopped
clearInterval(this.interval);
this.interval = null;
}
}
// this is the jQuery hook in order to run the animation the moment the document is ready
$(document).ready(
function(){
// the first two parameters are the two classnames of the elements
// the last two parameters are the delay between the animation repeating and the time taken for each animation (fade) to happen. The first one should always be bigger
myAnim.init("admin-stats-big-figures","admin-stats-big-figures-hidden",500,200);
}
);
OK, so now we need the HTML to compliment this (as I say I have added a little formatting):
<div><span class="admin-stats-big-figures">One</span> </div>
<div><span class="admin-stats-big-figures-hidden">Two</span> </div>
<hr/>
<input type="button" value="Start" onclick="myAnim.start()"/> | <input type="button" value="Stop" onclick="myAnim.stop()"/>
I have also provided buttons to stop/start the animation. You can see a working example at this JSFiddle - although the stop/start buttons are not working (presumably something specific to JSFiddle) they do work when in context though.
Here im gonna just replace your $(this). and maybe it'll work then.. also using callback.
function ChangeStats() {
$('body').find('.admin-stats-big-figures-hidden').fadeIn(500, function() {
$('body').find('.admin-stats-big-figures').fadeOut(500);
});
}
$(document).ready(function(){
setTimeout('ChangeStats()', 5000);
});
I have a page with a countdown in a DIV with id ="count"
I would like to monitor this div value so, when it reaches 0, a alert pops up.
I've gono so far as
if(parseInt(document.getElementById('count').innerHTML) < 2){}
But I don't know how to "listen" for the div changes
Can anyone help me?
Btw: it needs to be in pure javascript, with no such things as jquery.
Update:
I have no say so in the original code. It's an external page and I'm trying to run this code at the address bar
Presumably you have a function running based on setInterval or setTimeout. Have that function call your function when it gets to zero.
If you can't do that, you can try optimised polling - use setInterval to read the value, estimate when it might be near zero, check again and estimate when it might be zero, etc. When it is zero, do your thing.
There are DOM mutation events, but they are deprecated and were never well or widely supported anyway. Also, they are called when content changes so probably too often for your scenario anyway.
If you are changing the value of #count yourself then call the alert from that place. If not use:
window.setInterval(function(){
if(parseInt(document.getElementById('count').innerHTML) < 2) alert('Alarm!');
},1000); // 1s interval
UPDATE
To clear that interval:
var timer = window.setInterval(function(){
if(parseInt(document.getElementById('count').innerHTML) < 2) {
alert('Alarm!');
window.clearInterval(timer);
}
},1000); // 1s interval
//or by using non-anonymous function
function check(){
if(parseInt(document.getElementById('count').innerHTML) < 2) {
alert('Alarm!');
window.clearInterval(timer);
}
}
var timer = window.setInterval(check,1000);
The only efficient way to monitor this is to go to the code that is actually changing the div and modify it or hook it to call a function of yours whenever it updates the contents of the div. There is no universal notification mechanism for anytime the contents of div changes. You will have much more success looking into modifying the source of the change.
The only option I know of besides the source of the change would be using an interval timer to "poll" the contents of the div to notice when it has changed. But, this is enormously inefficient and will always have some of inherent delay in noticing the actual change. It's also bad for battery life (laptops or smartphones) as it runs continuously.
You don't listen for the div to change. The div is just there for a visual representation of the program's state.
Instead, inside whatever timing event is counting down the number, use a condition such as...
if (i < 2) {
// ...
}