Weird `$evalAsync` behaviour - javascript

Abstract
Hi, I'm trying to manipulate scroll logic of my angular application (by hand), Since there is no such thing as onLoaded event in angular, I was trying to accomplish that within $interval loop. It worked but UI was flickering, it looked like the scroll first goes all way up and then down again making everything look terrible. I've started looking for solution to my problem and found this answer. In which it was said that I have to be looking to $evalAsync which is capable of queueing an operation before UI does it's rendering. It lead me to the following code:
var restoreScroll = function (scroll) {
angular.element($window).scrollTop(scroll);
var fn = function () {
debugger;
var value = angular.element($window).scrollTop();
if (value !== scroll) {
angular.element($window).scrollTop(scroll);
$rootScope.$evalAsync(fn);
}
}
$rootScope.$evalAsync(fn);
};
which hangs and shows my misunderstanding of Angular's $evalAsync method.
Question
According to code above I need a method that would be trying to set the scroll until it succeeds, after when it should stop, how do you do that using $evalAsync?
Thank you in advance!
==================
Edit
Ive managed to make it work, however not without the devils (the $$postDigest) in my code, so here goes:
var restoreScroll = function (scroll) {
angular.element($window).scrollTop(scroll);
var fn = function () {
var value = angular.element($window).scrollTop();
if (value !== scroll) {
angular.element($window).scrollTop(scroll);
$rootScope.$$postDigest(fn);
}
}
$rootScope.$$postDigest(fn);
};
Is there a better way?

Related

Why does waitForKeyElements() only trigger once despite later changes?

For several years I've used the waitForKeyElements() function to track changes in webpages from a userscript. However, sometimes I've found it doesn't trigger as expected and have worked around out. I've run into another example of this problem, and so am now trying to figure out what the problem is. The following is the barest example I can create.
Given a simple HTML page that looks like this:
<span class="e1">blah</span>
And some Javascript:
// function defined here https://gist.github.com/BrockA/2625891
waitForKeyElements('.e1', handle_e1, false);
function handle_e1(node) {
console.log(node.text());
alert(node.text());
}
setInterval(function() {
$('.e1').text("updated: "+Math.random());
}, 5000);
I would expect this code to trigger an alert() and a console.log() every 5 seconds. However, it only triggers once. Any ideas?
Here's a codepen that demonstrates this.
By design and default, waitForKeyElements processes a node just once. To tell it to keep checking, return true from the callback function.
You'll also want to compare the string (or whatever) to see if it has changed.
So, in this case, handle_e1() would be something like:
function handle_e1 (jNode) {
var newTxt = jNode.text ();
if (typeof this.lastTxt === "undefined" || this.lastTxt !== newTxt) {
console.log (newTxt);
this.lastTxt = newTxt;
}
return true; // Allow repeat firings for this node.
}
With the constant string comparisons though, performance might be an issue if you have a lot of this on one page. In that scenario, switching to a MutationObserver approach might be best.

Can a Shiny app respond to a control without going to the server?

I'm writing an rgl widget within the htmlwidgets framework so that rgl scenes can be used for output in Shiny apps. Things are basically working (though it's still rough around the edges; see the rglwidget package on http://R-forge.r-project.org ), but it's not nearly as responsive as the native Javascript controls that are already in rgl.
I suspect the problem is the round trip to the server.
Sometimes this will be unavoidable: if you want to make big changes to a scene, you may want to do a lot of calculations in R. But in other cases (the ones covered by the native controls), there's no need for R to be involved, everything can be done in Javascript.
I don't want to duplicate all the work that has gone into writing the Shiny input controls, but I'd like to use them. So my question is:
Is there a way to tell a Shiny input to call a Javascript function when it is changed, not to send its value to the server to be used in a Shiny output?
The answer to my question is "Yes"! It's actually fairly straightforward, at least if the control uses the htmlwidgets framework.
Warning: I'm not very experienced in Javascript, so this may not be very good Javascript style. Please let me know if so, and I'll improve it.
Here's the idea: If I have a Shiny sliderInput() control with inputId = "foo", then my own Javascript code can get it using window["foo"], and can set an "onchange" event handler on it. When that event handler is triggered, I can read the "value" property, and send it to my controls.
If I don't use the reactive inputs from the slider, I don't get the delay from going to the server.
Here's my current renderValue function for the widget:
renderValue: function(el, x, instance) {
var applyVals = function() {
/* We might be running before the scene exists. If so, it
will have to apply our initial value. */
var scene = window[x.sceneId].rglinstance;
if (typeof scene !== "undefined") {
scene.applyControls(x.controls);
instance.initialized = true;
} else {
instance.controls = x.controls;
instance.initialized = false;
}
};
el.rglcontroller = instance;
if (x.respondTo !== null) {
var control = window[x.respondTo];
if (typeof control !== "undefined") {
var self = this, i, oldhandler = control.onchange;
control.onchange = function() {
for (i=0; i<x.controls.length; i++) {
x.controls[i].value = control.value;
}
if (oldhandler !== null)
oldhandler.call(this);
applyVals();
};
control.onchange();
}
}
applyVals();
},

Angular $watch window.pageYOffset not updating?

In my controller, I have:
$scope.woffset = window.pageYOffset;
$scope.$watch("woffset", function (newValue, oldValue) {
console.log("hello");
console.log(window.pageYOffset);
}, true);
});
So when I scroll, I should be receiving console logs of "hello" as the pageYOffset changes. However, it doesn't do anything. But if I run window.pageYOffset in the console as I scroll down, I can see that the value is changing. Any ideas?
I've tried multiple variations of watch (with and without true, using functions instead of strings, but nothing seems to work).
(I know there is a work around with onscroll, but I'd like to learn how it would work this way) Thanks!
Edit: This doesn't seem to work either:
$scope.test = function () {
return window.pageYOffset;
}
$scope.$watch("test", function (newValue, oldValue) {
console.log("hello");
console.log(window.pageYOffset);
}, true);
The issue is that $scope.woffset is being set to a normal number - it's never changing. It's like doing the following:
var i = 5;
var j = i;
i = 7;
// Wondering here why j isn't 7
There are 2 ways to solve your problem - the naive way or the more efficient way.
First:
window.onscroll = function () {
console.log("hello");
console.log(window.pageYOffset);
// any $scope variable updates
$scope.$digest();
};
However, with this solution, every time you scroll this will be running like crazy. The more efficient way would be to use the above solution combined with some "debouncing" - check out this Stack Overflow question to learn about doing that Can I debounce or throttle a watched <input> in AngularJS using _lodash?
One problem this has is it will never un-watch scrolling, even once the controller is garbage collected. To solve this problem, also do the following:
$scope.$on('$destroy', function() {
window.onscroll = null;
});
One good thing to verify is that your window.pageYOffset value is actually changing. Scroll the window then execute a window.pageYOffset in the console to verify it is changing. It could be that you are scrolling a child container and not the window so you are not seeing any change.
angular.element($window).on("scroll resize", function (e) {
console.log($window.pageYOffset)
});
scope.$on('$destroy', function () {
angular.element($window).unbind("scroll resize");
});

Js Animation using setInterval getting slow over time

I have a web site which displays a portfolio using this scrollbar http://manos.malihu.gr/jquery-custom-content-scroller/ .
I want to have an automatic scroll when the page is
fully loaded. When a user pass over the portfolio it stops the scroll and whe, he leaves it starts moving again.
It currently works well but sometimes it's getting slow or sluggish randomly.
Is there something wrong with my js function?
Here's the page having problems : http://www.lecarreau.net/new/spip.php?rubrique5 (I need the change the hosting provider, I know the site is slow).
This is the function:
(function($){
var timerId = 0;
$(window).load(function(){
$("#carousel").show().mCustomScrollbar( {
mouseWheel:false,
mouseWheelPixels:50,
horizontalScroll:true,
setHeight:370,
scrollButtons:{
enable: true,
scrollSpeed:50
},
advanced:{
updateOnContentResize:true
}
});
timerId = setInterval(function(){myTimer()},50);
$('#carousel').mouseenter(function () {
clearInterval(timerId);
});
$('#carousel').mouseleave(function () {
timerId = setInterval(function(){myTimer()},50);
});
});
})(jQuery);
function myTimer()
{
var width = $(".mCSB_container").width();
var left = $(".mCSB_container").position().left;
if (width-left>0) {
var scrollTo = -left+4;
$("#carousel").mCustomScrollbar("scrollTo", scrollTo);
}
}
Is there something obvious I'm missing?
Timing in the browser is never guaranteed. Since all functionality contents for time on a single event loop, continuous repeated tasks sometimes get a little janky.
One thing you could do that might decrease the jank is to cache the jQuery objects you're creating every 50ms:
var mCSBContainerEl = $(".mCSB_container");
var carouselEl = $("#carousel")
function myTimer()
{
var width = mCSBContainerEl.width();
var left = mCSBContainerEl.position().left;
if (width-left>0) {
var scrollTo = -left+4;
carouselEl.mCustomScrollbar("scrollTo", scrollTo);
}
}
Selecting these to elements only needs to happen once. From there they can just reference a variable instead of having to find the element on the page and wrap it in jQuery again and again.
Caching the variable in this case depends on them being in the DOM when the code runs, which isn't guaranteed here. You may have to move the variable assignment and function declaration into the function passed to load.
Also, as John Smith suggested, consider checking out requestAnimationFrame. Here's a shim I use by Paul Irish: http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/

Waiting for model's data to be loaded using jQuery when -- experiencing timing differences?

I have an MVC application. I am trying to load a model from the server using jQuery's load. This works perfectly fine. I am now trying to run some JavaScript after all of my views have been loaded. As such, I am introducing jQuery's deferred promise functionality through use of jQuery .when
My limited understanding of this functionality has lead me to believe that the two bits of code below should run identically. It seems to me that my 'then' method is executing too soon, though. I'm not sure how to confirm that.
Old Code (Works):
$('#OrderDetails').load('../../csweb/Orders/OrderDetails', function () {
$("fieldset legend").off('click').click(function () {
var fieldset = $(this).parent();
var isWrappedInDiv = $(fieldset.children()[0]).is('div');
if (isWrappedInDiv) {
fieldset.find("div").slideToggle();
} else {
fieldset.wrapInner("<div>");
$(this).appendTo($(this).parent().parent());
fieldset.find("div").slideToggle();
}
});
});
Now, I would like to extend that to wait for multiple load events. To keep things simple, though, I am just going to try and wait for OrderDetails:
New Code (Doesn't Work):
var onDisplayLoadSuccess = function () {
console.log("Done!");
console.log('Fieldset legend:', $('fieldset legend'); //Not all found.
$("fieldset legend").off('click').click(function () {
var fieldset = $(this).parent();
var isWrappedInDiv = $(fieldset.children()[0]).is('div');
if (isWrappedInDiv) {
fieldset.find("div").slideToggle();
} else {
fieldset.wrapInner("<div>");
$(this).appendTo($(this).parent().parent());
fieldset.find("div").slideToggle();
}
});
};
var onDisplayLoadFailure = function () {console.error("Error.");};
$.when($('#OrderDetails').load('../../csweb/Orders/OrderDetails')).then(onDisplayLoadSuccess, onDisplayLoadFailure);
I do not see any errors fire. I see 'Done' print to the console, but the timing seems to be different. Legends which existed on the page prior to calling when/load/then have the click event applied to them, but legends which are loaded from in the view given back by OrderDetails do not have the click event bound to them.
By contrast, the old code's success function applied the click event to all legends appropriately. Why would this be the case?
To capture events on DOM elements that are added dynamically after binding an Event, you need to delegate it (http://api.jquery.com/on/).
Something like:
$('fieldset').on('click', 'legend', function(){

Categories