lodash debounce not-working (I use it wrongly?), function called multiple times - angularjs

UPDATE: Answered by myself. Scroll to the end.
My objetive is simple:
I have a google map canvas. User can drag the map, and the program automatically downloads the incidents around the center of the map (and draw the markers).
I have this listener for plugin.google.maps.event.CAMERA_CHANGE events. The thing is, this listener triggers multiple times. Meaning: from the time you tap your finger on the canvas -> dragging in accros the canvas -> to the time you lift your finger ... the event triggers multiple times. Not just when you lift your finger.
Apparently it has a watcher that triggers every N miliseconds.
I dont't want my code to perform that costly downloading data from the server + drawing markers during those interim camera_changes. I want to do it only after the user stop dragging. That means: on the last camera change event received during 5 seconds (I figured that the slowest user takes 5 seconds to drag from corner to the opposite corner of the canvas).
Obviouly I turn to debounce for this need. But it doesn't seem to work. I can see from the logs (X1, X2).... that the function gets called multiple times (around 3-to-4 times, depends on how fast you drag across the canvas).
Indeed, they get called only after I stop dragging. But, they get called in series. All 3-4 of them. With delay of 5 seconds between invocation.
That's not what I expected. I also added invocation to the .cancel method (which I think is superfluous..., because if I understand it correctly, the debounce should've already handled that; cancelling interim-invocations within the timeout).
I also tried throttle (which I think conceptually is not the answer. Debounce should be the answer). Anyway, same issue with throttle.
So, my question: where did I do wrong (in using lodash's debounce)?
Thanks!
var currentPosition = initialPosition();
drawMarkersAroundCenter(map, currentPosition);
var reactOnCameraChanged = function(camera) {
console.log('X1');
console.log('-----');
console.log(JSON.stringify(camera.target));
console.log(JSON.stringify(currentPosition));
console.log('-----');
if (camera.target.lat == currentPosition.lat && camera.target.lng == currentPosition.lng) {
return;
}
currentPosition = camera.target;
drawMarkersAroundCenter(map, currentPosition);
}
var debouncedReactOnCameraChange = lodash.debounce(reactOnCameraChanged, 5000, {
'leading': false,
'trailing': true
});
map.on(plugin.google.maps.event.CAMERA_CHANGE, function(camera) {
debouncedReactOnCameraChange.cancel();
debouncedReactOnCameraChange(camera);
});
--- UPDATE ---
I tried a very simplified scenario of using debounce on nodejs console, it works as I expected. I don't even invoke .cancel in the code below. So what's wrong with the above code? I can't see any difference with this simplified code in the image below.
UPDATE
I tried with this dude method instead of "reactOnCameraChanged":
var dude = function(camera) {
console.log('dude');
}
var debouncedReactOnCameraChange = lodash.debounce(dude, 5000, {
'leading': false,
'trailing': true
});
And I also removed the invocation to .cancel:
map.on(plugin.google.maps.event.CAMERA_CHANGE, function(camera) {
//debouncedReactOnCameraChange.cancel();
debouncedReactOnCameraChange(camera);
});
I can see the 'dude' gets printed only once during those 5 seconds.... So.., something that I do inside reactOnCameraChanged is causing interference ... somehow....
RESOLVED:
See answer below.

This code works:
var currentPosition = latLng;
drawMarkersAroundCenter(map, currentPosition);
var debouncedReactOnCameraChange = lodash.debounce(function(camera) {
console.log('reactOnCameraChanged: ' + JSON.stringify(currentPosition));
drawMarkersAroundCenter(map, currentPosition);
}, 3000, {
'leading': false,
'trailing': true
});
map.on(plugin.google.maps.event.CAMERA_CHANGE, function(camera) {
console.log('CAMERA_CHANGE');
if (camera.target.lat == currentPosition.lat && camera.target.lng == currentPosition.lng) {
console.log('same camera spot');
return;
}
console.log('different camera spot');
currentPosition = camera.target;
debouncedReactOnCameraChange(camera);
});

Related

Protractor - Unable to access element due to fixed Top navigation bar

I'm facing the following issue in protractor with jasmine
Click/mouse hover not working because of fixed top navigation bar in my application. I need to click/perform mouse hover on a web page.
Unfortunately that element is displaying behind that fixed navigation bar. So scroll till element present & click by x & y coordinates are not working.
My dependencies are :
protractor version 5.2.2
node 8.9.3
selenium standalone 3.13
chrome driver-2.40
chromebrowser v67
OS- Windows 10
Thanks in advance
Try using prototype executeScript
Just try clicking that element from the browser console using id,name or xpath.
For example :
var el = element(by.module('header'));
var tag = browser.executeScript('return arguments[0].click()', el).then(function() {
expect(something).toMatch(something);
});
Another way, along the same lines as what Bharath Kumar S and knowing JeffC's caveat that this approach is cheating, I had a similar issue where the App-Header kept getting in my way of clicking, and I knew I was willing to never need it (so, for instance, to find other ways to navigate or log out and not check for stuff that was on it). I, therefore, did the following, which solved the problem. Note if you refresh the screen, you have to call it again. Also note I am using a number of functions from https://github.com/hetznercloud/protractor-test-helper, which do what you would expect from their names.
var removeAppHeaderIfAny = async function() {
//this function hides the app header
//it is useful to avoid having covers there when Protractor worries that something else will get the click
let found = false;
try {
found = await waitToBeDisplayed(by.className("app-header"), 2000);
} catch (e) {
let s: string = "" + e;
if (s.search("TimeoutError") != 0) flowLog("presumably fine, cover already removed: " + e);
found = false;
}
if (!found) return;
if (found) {
let coverElement = await element(by.className("app-header"));
browser.executeScript(
"arguments[0].style.visibility='hidden';",
coverElement
);
await waitToBeNotDisplayed(by.className("app-header"), 10000);
}
return;
//note after this is called you will not see the item, so you cannot click it
};
As I look at the code, it strikes me one can probably remove the if (found) and associated brackets at the end. But I pasted in something I know has been working, so I am not messing with that.
As indicated up front, I knew I was willing to forego use of the app-header, and it is a bit crude.

How to invoke a function at a trigger point?

When a trigger point is reached, I just want to call a function or run a few statements to do something, which has nothing to do with interface. Here is how I am doing now for this purpose:
var scene = new ScrollMagic.Scene({triggerElement: "#some-trigger", triggerHook: 'onLeave', offset: })
.on('start', function () {
// statements go here
}).addTo(controller);
Is this the correct way?
The reason to ask this question is that I find out Scroll Magic can be a UNIQUE event control tool.
Little later but I jump to that question because I had the same issue in some projects.
So i solved in two ways:
Using enter or progress event
ENTER is trigger when a scene is starting. It's depends on triggerHook
scene.on("enter", function (event) {
console.log("Scene entered.");
});
PROGRESS is trigger every time
scene.on("progress", function (ev) {
if( ev.progress > 0.25){
//== going farward
}
if(ev.progress < 0.25){
//== going backward
}
})

Sometimes svg chart doesn't fit in the container but it works when screen refresh

Sometimes svg chart appear more small than normal. This problem is solved Reloading Screen.
//Values Graphic
$scope.$watch(vm.dataGraphic.watch, function () {
var data = vm.dataGraphic.watch ? $scope.$eval(vm.dataGraphic.watch) : vm.dataGraphic;
setTimeout(sleep,500); //patch to "solve" this issue
function sleep(){
vm.dataValues = getDataValues(data);
}
});
function getDataValues(data) {
vm.dataGraphic = data || dataGraphicTest;
if (vm.dataGraphic.values.length == 0) {
return [];
} else {
vm.dataKeyValues = transformForKeyValues(vm.dataGraphic.values, vm.dataGraphic.accumulated);
vm.barValues = transformBarValues(vm.dataGraphic.values, vm.dataGraphic.limit);
var lineValues = transformLineValues(vm.barValues, vm.dataGraphic.limit, vm.dataGraphic.accumulated, vm.dataGraphic.startMonthlyLimit);
vm.maxY = calculateMaxY(vm.barValues, lineValues);
return [
{
"key": vm.dataGraphic.labelX.name,
"bar": true,
"color": _graphicsColors.bar,
"values": vm.barValues
},
{
"key": _graphicsValorPorDefecto,
"color": _graphicsColors.line,
"values": lineValues
}
];
}
}
SVG element with the following html tags appears with wrong dimensions.
<g class="nvd3 nv-wrap nv-linePlusBar" transform="translate(35,10)">
This problem does not always happen, but when it happens is arranged refreshing the screen.
I think this patch is a bad idea and I would like to understand what is happening .
Thanks
I think your problem ultimately stems from your watch:
$scope.$watch(vm.dataGraphic.watch, function () {
var data = vm.dataGraphic.watch ? $scope.$eval(vm.dataGraphic.watch) : vm.dataGraphic;
setTimeout(sleep,500); //patch to "solve" this issue
function sleep(){
vm.dataValues = getDataValues(data);
}
});
The problem I see is that you're using setTimeout to call your sleep function a half second after the watch fires. As it is now, this will be fine if your $digest is still running in 500ms. setTimeout is a vanilla JavaScript function, so calling it won't notify angularjs that there are any changes after the $digest has ended. Angular only triggers a $digest after 'user-initiated' events like ajax, clicks, entering data, etc. (read more here). If the $digest is running, you'll get lucky and angularjs will just happen to see those changes. You'll need to inform angular of your changes using $scope.$apply:
$scope.$watch(vm.dataGraphic.watch, function () {
var data = vm.dataGraphic.watch ? $scope.$eval(vm.dataGraphic.watch) : vm.dataGraphic;
setTimeout(sleep,500); //patch to "solve" this issue
function sleep(){
vm.dataValues = getDataValues(data);
$scope.$apply();
}
});
There are a lot of ways to use $scope.$apply, so be sure to check out the documentation. Also do a search on something like 'when to use $scope.$apply' and you'll find a lot of people for and against it. It does trigger another $digest loop, which can be costly if you have a lot of bindings or if you're at the end of the final digest loop and apply causes it to start over.
I don't think it's actually a good idea to be updating your model using setTimeout from within a watch function. Does this not work without it? You may have some other async code that needs to be applied in the same fashion. Generally, you want $scope.$apply to be as close to the non-angular asynchronous code as possible.

how to notify about $interval end in angularjs

I want to make custom animation in my web application, and i'm using $interval method to do some visual effects.
And all i want is to know, when that animation ends.
For example
var myInterval = $interval(function(){
//do some staff
}, 10, 20);
How can i get notify about that interval ends? Except $timeout(fn, 200), of course.
And the second question is also about notifying, but in case, when i cancel that interval is other place manually by $interval.cancel(myInterval), can i get notified about that?
For the first case, you can just do:
myInterval.then(function () { console.log("Finished."); } );
It isn't a good idea to count the number of iterations when Angular is already doing that for you.
See https://docs.angularjs.org/api/ng/service/$q
You can broadcast an event by yourself when canceling the interval. Take a look at $rootScope.$broadcast().
Where you cancel the interval:
$rootScope.$broadcast('Custom::Event');
Where you want to retrieve the broadcast:
$scope.$on('Custom::Event', function (e) { ... });
Edit: Iterations
If you want to send the broadcast after the last iteration, check the first parameter provided to the function for the $interval.
$interval(function (iteration) {
// Do some stuff...
// Broadcast after last iteration
if (iteration === iterations - 1) {
$scope.$broadcast('Interval::Finished');
}
}, delay, iterations);
See: JS Bin

WMS GetFeatureInfo; multiple layers, different sources

I'm developing a web application using GeoExt, OpenLayers and having my own GeoServer to serve various maps. Still, I want to let the user add other WMS's if needed, to be able to play around with all desired layers.
Thus, my problem with the GetFeatureInfo request. Right now I have a toolbar button attached to geoext's map panel,
new GeoExt.Action({
iconCls: "feature",
map: map,
toggleGroup: "tools",
tooltip: "Feature",
control: featureControl
})
its control attribute being
var featureControl = new OpenLayers.Control.WMSGetFeatureInfo({
queryVisible: true,
drillDown: true,
infoFormat:"application/vnd.ogc.gml"
});
I've also defined an event listener to do what I really want once I receive the responses, but that is not relevant here. My problem is the following:
Considering the user clicks on a point where there are 2+ visible layers and at least one of them is from a different source, OpenLayers will have to do one AJAX request per different source and, from OpenLayers own documentation,
Triggered when a GetFeatureInfo response is received. The event
object has a text property with the body of the response (String), a
features property with an array of the parsed features, an xy property
with the position of the mouse click or hover event that triggered the
request, and a request property with the request itself. If drillDown
is set to true and multiple requests were issued to collect feature
info from all layers, text and request will only contain the response
body and request object of the last request.
so, yeah, it will obviously wont work like that right away. Having a look at the debugger I can clearly see that, giving two layers from different sources, it actually DOES the request, it's just that it doesn't wait for the first's response and jumps for the next one (obviously, being asynchronous). I've thought about doing the requests one-by-one, meaning doing the first one as stated above and once it's finished and the response saved, go for the next one. But I'm still getting used to the data structure GeoExt uses.
Is there any API (be it GeoExt or OpenLayers) option/method I'm missing? Any nice workarounds?
Thanks for reading :-)
PS: I'm sorry if I've not been clear enough, english is not my mother tongue. Let me know if something stated above was not clear enough :)
i Hope this help to someone else, I realized that: you're rigth this control make the request in asynchronous mode, but this is ok, no problem with that, the real problem is when the control handle the request and trigger the event "getfeatureinfo" so, i modified 2 methods for this control and it works!, so to do this i declare the control first, and then in the savage mode i modified the methods here is de code:
getInfo = new OpenLayers.Control.WMSGetFeatureInfo({ drillDown:true , queryVisible: true , maxFeatures:100 });
//then i declare a variable that help me to handle more than 1 request.....
getInfo.responses = [];
getInfo.handleResponse=function(xy, request) { var doc = request.responseXML;
if(!doc || !doc.documentElement) { doc = request.responseText; }
var features = this.format.read(doc);
if (this.drillDown === false) {
this.triggerGetFeatureInfo(request, xy, features);
} else {
this._requestCount++;
this._features = (this._features || []).concat(features);
if( this._numRequests > 1){
//if the num of RQ, (I mean more than 1 resource ), i put the Request in array, this is for maybe in a future i could be need other properties or methods from RQ, i dont know.
this.responses.push(request);}
else{
this.responses = request;}
if (this._requestCount === this._numRequests) {
//here i change the code....
//this.triggerGetFeatureInfo(request, xy, this._features.concat());
this.triggerGetFeatureInfo(this.responses, xy, this._features.concat());
delete this._features;
delete this._requestCount;
delete this._numRequests;
// I Adding this when the all info is done 4 reboot
this.responses=[];
}
}
}
getInfo.triggerGetFeatureInfo= function( request , xy , features) {
//finally i added this code for get all request.responseText's
if( isArray( request ) ){
text_rq = '';
for(i in request ){
text_rq += request[i].responseText;
}
}
else{
text_rq = request.responseText;
}
this.events.triggerEvent("getfeatureinfo", {
//text: request.responseText,
text : text_rq,
features: features,
request: request,
xy: xy
});
// Reset the cursor.
OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");}
Thanks, you bring me a way for discover my problem and here is the way i solved, i hope this can help to somebody else.
saheka's answer was almost perfect! Congratulations and thank you, I had the same problem, and with it I finally managed to solve it.
What I would change in your code:
isArray() does not work, change it like this: if(request instanceof Array) {...} at the first line of getInfo.triggerGetFeatureInfo()
to show the results in a popup this is the way:
My code:
getInfo.addPopup = function(map, text, xy) {
if(map.popups.length > 0) {
map.removePopup(map.popups[0]);
}
var popup = new OpenLayers.Popup.FramedCloud(
"anything",
map.getLonLatFromPixel(xy),
null,
text,
null,
true
);
map.addPopup(popup);
}
and in the getInfo.triggerGetFeatureInfo() function, after the last line, append:
this.addPopup(map, text_rq, xy);
A GetFeatureInfo request is send as a JavaScript Ajax call to the external server. So, the requests are likely blocked for security reasons. You'll have to send the requests to the external servers by a proxy on your own domain.
Then, configure this proxy in openlayers by setting OpenLayers.ProxyHost to the proper path. For example:
OpenLayers.ProxyHost = "/proxy_script";
See http://trac.osgeo.org/openlayers/wiki/FrequentlyAskedQuestions#ProxyHost for more background information.

Resources