AJAX and Leaflet: Inspect feature properties before styling/adding to map - ajax

I'm using leaflet-ajax to load geoJSON on demand. I want to find the maximum theProperty value so I can use that to scale the feature's fill colors before I add them to the map.
Here's my general approach:
function maxBinPropertyValue(theProperty) {
var theGeoJson = null;
var maxPropertyValue = 0;
var propertyValue = null;
var theGeoJson = new L.GeoJSON.AJAX(binsFileName());
theGeoJson.on('data:loaded', function() {
console.log('The data is loaded');
theGeoJson.eachLayer(function(layer) {
console.log('Looping through the layers');
propertyValue = feature.properties[theProperty];
if (propertyValue > maxPropertyValue) {
maxPropertyValue = propertyValue;
console.log('Max so far: ' + maxPropertyValue);
};
});
});
theGeoJson = null;
console.log('The final maximum value: ' + maxPropertyValue);
return maxPropertyValue;
};
I'm trying to wait for the data:loaded event, then loop through all the features to find the maximum value of theProperty, which is returned to the calling routine.
Except it doesn't work. The first console.log says 'The data is loaded'. The second and third console.logs are never reached, and the fourth and final one reports a value of 0 for maxPropertyValue.
How can I examine all the features in a featureset before styling them, in a way guaranteed to not have asynchronous problems?
PS: I'm pretty sure I can't use onEachFeature: instead of the above approach, because I need to examine every feature's property to determine the maximum value in the set before I can style any of the features.

As for your issue about inspecting your data and retrieving the maximum value, you are indeed facing the classic asynchronous concept of JavaScript.
See How do I return the response from an asynchronous call?
Asynchronism is a problem if not dealt with properly, but an advantage if correctly handled.
To put the concept shortly, you do not manage asynchronism in a "standard" sequential way, but you should rather consider parts of code (callbacks) that are executed at a later time based on events.
Whenever you provide a function as an argument, it is certainly a callback that will be executed at a later time, but very probably much later than the next instructions.
So in your case, your 2nd and 3rd console.log are within a callback, and will be executed once your data is loaded, which will happen much later than your 4th console.log.
As for your next step (styling and adding to map), you actually do not need to perform an extra AJAX call, since you already have all data available in theGeoJson variable. You simply need to refactor / restyle it properly.
It is a good approach to break your problem in small steps indeed.
Good luck!
PS: that being said, ES7 provides async and await functionalities that will emulate a sequential execution for asynchronous functions. But to be able to use those, you need latest browser versions or transpilation, which is probably more work to learn and configure as of today for a beginner than understanding how to work with async JS.

I also had this problem and had to wrap my head around this, so giving an explicit example for solution here;
// make a request with your "url"
var geojsonLayer = new L.GeoJSON.AJAX("url");
// define your functions to interact with data
function thingToDoBeforeLoadingStarts () {
// do stuff
}
function thingToDoForEachFileDownloaded () {
// do stuff
}
function thingToDoAfterAllDownloadEnds () {
// do stuff
}
// attach listeners
geojsonlayer.on("data:loading",thingToDoBeforeLoadingStarts);
geojsonLayer.on("data:progress",thingToDoForEachFileDownloaded)
geojsonLayer.on("data:loaded",thingToDoAfterAllDownloadEnds);

Related

How do you use the dc.redrawAll() function onclick?

I would like to be able to use the dc.js select menu (dc.selectMenu) in such a way that when I click on an element it gets the value of said element and that becomes the value of the select, once selected it should refresh the data as it normally would if you had just selected in the first place.
The problem I'm having is that I can set the value, but dc.redrawAll() seems to do nothing for me so I think I must be filtering wrongly, but I can't find much information online regarding how to do it other than simply using the filter method (not onclick).
I have tried to set the destination to whatever data-destination is which appears to be working, the value of the select does update when I check with console.log to check the value of the select menu, I then use the dc.redrawAll() function expecting it would filter based on the select option but it does nothing (not even an error in the console)
My function so far is looking like:
function select_destination(ndx) {
var destination_dim = ndx.dimension(dc.pluck('destination'));
var destination_group = destination_dim.group();
var destination = null;
document.addEventListener('click', function(e) {
if (!e.target.matches('.open-popup-link')) return;
e.preventDefault();
var destination = e.target.getAttribute('data-destination').toString();
document.getElementById('select-destination').value = destination;
dc.redrawAll();
});
dc.selectMenu('#select-destination')
.dimension(destination_dim)
.group(destination_group)
.filter(destination);
}
I would expect the graphs to update based on the select option but nothing happens, and I get no error message to go off either.
I suspect I'm using dc.redrawAll() wrongly as if I go to the console and type dc.redrawAll(); I get undefined but I'm really at a loss now and the documentation isn't really helping me at this point so I don't know what else to do.
they are bits of your code that I don't quite understand, for instance why do you have have filter(destination /*=null */)
anyway, So you want to filter the select menu? you can call directly the replaceFilter function with the value, as done in the source code:
menu.replaceFilter(destination);
dc.events.trigger(function () {
menu.redrawGroup();
});
See the source code for the full example of how it's done
https://dc-js.github.io/dc.js/docs/html/select-menu.js.html#sunlight-1-line-129
as for why it doesn't work, I have had some surprising results mixing d3 with pure dom js. Try to rewrite your even handler in d3, eg
d3.select('#select-destination').property('value', destination);
it's possibly that changing the value on the dom directly isn't triggering the change event.
My experience with d3 is that it works better to change the underlying data (call directly filter functions or whatever you want to do) and let dc redraw the needed rather than manipulating the dom directly

Is there anything wrong with this pattern for a JS library?

I admittedly know little about the inner workings of javascript, but need to make a library and would like to learn (hence asking here). I understand using the closure and exporting to window to not pollute the global namespace, but beyond that it confuses me a bit.
(function() {
var Drop = window.Drop = function() {
var files = [];
var add = function(word) {
files.push(word);
return files;
}
return {
files: files,
add: add
}
}
})()
// All of these seem to be the same?
var a = Drop();
var b = new Drop();
var c = new Drop;
// Each has their own state which is what I want.
a.add("file1");
b.add("file2");
c.add("file3");
Why are all three ways of "initializing" Drop the same?
What exactly gives them the ability to have their own state?
Is there an alternative to the return syntax to export those functions on Drop?
Is there just a flat out better best practice way of creating a self contained library like this?
I have searched around the net, but have found very little consistency on this subject.
The first way (Drop()) just calls the function as normal, so this is the global object (window in browser environments). It does its stuff and then returns an object, as you'd expect.
The second way (new Drop()) creates a new Drop object and executes the constructor with this set to that object. You do not, however, use this anywhere and return an object created from an object literal, so the Drop object is discarded and the object literal returned instead.
The third way (new Drop) is semantically the same as the second; it is only a syntactic difference.
They all have their own state because each time you call Drop, it has its own set of local variables distinct from the local variables of any other call to Drop.
You could transform your code to use the normal new syntax and prototypes. This has a few advantages: namely, you only create the add function once rather than one for each Drop call. Your modified code might look like this:
function Drop() {
this.files = [];
}
Drop.prototype.add = function(word) {
this.files.push(word);
return this.files;
};
By doing this, though, you lose being able to call it without new. There is, however, a workaround: You can add this as the first line inside function Drop:
if(!(this instanceof Drop)) {
return new Drop();
}
Since when you call it with new, this will be a Drop, and when you call it without new, this will be something other than a Drop, you can see if this is a Drop, and if it is, continue initializing; otherwise, reinvoke it with new.
There is also another semantic difference. Consider the following code:
var drop = new Drop();
var adder = drop.add;
adder(someFile);
Your code will work here. The prototype-based code will not, since this will be the global object, not drop. This, too, has a workaround: somewhere in your constructor, you can do this:
this.add = this.add.bind(this);
Of course, if your library's consumers are not going to pull the function out of the object, you won't need to do this. Furthermore, you might need to shim Function.prototype.bind for browsers that don't have it.
No. It's all a matter of taste.
Why are all three ways of "initializing" Drop the same?
// All of these seem to be the same?
var a = Drop();
var b = new Drop();
var c = new Drop;
When you use new in JavaScript to invoke a function, the value of this inside the function becomes the new object.
But the reason they're the same in your case is that you're not using this at all. You're making a separate object using object literal syntax, and returning it instead, so the new has no impact.
What exactly gives them the ability to have their own state?
Because each function invocation makes a new object, each object is entirely different for each invocation.
The functions assigned to the object are recreated in each Drop invocation, and therefore create a closure over the enclosing variable scope. As such, the files array of each invocation is continuously accessible to the functions made in each respective invocation.
Is there an alternative to the return syntax to export those functions on Drop?
Yes. Assign the functions and array to this, and remove the return statement. But that will require the use of new. Alternatively, put the functions on the .prototype object of Drop, and they'll be shared among all instances made using new, but keep the array assigned to this in the constructor so that it's not shared.
For the prototyped functions to reference the array, they would use this.files.
Is there just a flat out better best practice way of creating a self contained library like this?
JavaScript is very flexible. There are many ways to approach a single problem, each with its own advantages/disadvantages. Generally it'll boil down to taking advantage of closures, of prototypal inheritance, or some combination of both.
Here's a full prototypal inheritance version. Also, the outer (function() {})() isn't being used, so I'm going to add a variable to take advantage of it.
(function() {
var totalObjects = 0; // visible only to functions created in this scope
var Drop = window.Drop = function() {
this.files = [];
this.serialNumber = totalObjects++;
}
Drop.prototype.add = function(word) {
this.files.push(word);
return this.files;
};
})();

How to code against logic that depends on the current time in AngularJS?

I have some code and the result of it depends on the current time. Say,
Shop.prototype.isOpen = function() {
var now = new Date();
var today = now.getDay();
return this.openTime(today) <= now && now <= this.closeTime(today);
};
And then in the view, we display whether a shop is open:
<span ng-show="shop.isOpen()">Open now!</span>
The isOpen method is called once and doesn't get updated after that.
I have lots of complex application logic that depends on the isOpen and similar "time-bound" data.
What are the general approaches to keep the isOpen data fresh and have application logic/view be constantly in sync with that?
I think one solution would be to have an intermediate object whose value gets updated in frequent intervals, but I'm not sure if this is the right approach.
The angular documentation on directives has an example of time being updated.
http://docs.angularjs.org/guide/directive
But basically, have your controller set a $scope (or $rootScope, depending on how you want to access it) property that gets updated via a setTimeout loop.

Not uniform argument order in backbone event triggers. How can I work around it?

someModel.bind("all", function(eventName, model, XXXXX, YYYYY) {
options.myStuff = "MyStuff et all";
self.trigger(eventName, model, ??????, ?????);
}
XXXXX, YYYYY, ????? mark the problem: Where are the options?
I want to augment the options and retrigger any event on a new context. The problem is that backbone puts options in 4th position in some events ("change:xxx", "error", etc..) and in 3rd position in some others("change", "reset") making it impossible for me to know where in the arguments the options are, and to know where in the trigger i should put the enhanced options. Is there any other way except the obvious separate snippet for every individual event?
UPDATE: Looking at the backbone source, an acceptable solution for me would be to patch Backbone to always include a third argument in every event, so that options are always in the same 4th position. It is a simple change but I am kind of hoping for a more user space solution.
As far as I can tell, the options are always the last argument passed to the callback. You could use the arguments object of the callback to modify the options. https://developer.mozilla.org/en/JavaScript/Reference/Functions_and_function_scope/arguments
For example
var x=new Backbone.Model();
x.bind("all", function(e) {
console.log("x : "+e);
console.log(arguments[arguments.length-1]);
});
var m=new Backbone.Model( {name:"a"} );
m.bind("all",function(e){
console.log("m : "+e);
var args=Array.prototype.slice.call(arguments);
args[args.length-1].modified=true;
x.trigger.apply(x,args);
});
m.set({name:"b"});

Is there a way to break out of event recursion in jQuery without using a global variable?

I have a form with 2 text inputs and 2 span controls. Normally, when textbox A is changed an event is fired to change span A, and when textbox B is changed, an event is fired to change span B.
However, in one particualar case I would like a change either textbox A or textbox B to update both span A and B. I tried wiring the events up to the corresponding controls in this case, but it didn't work because there is much state that is set up in the event building code (not to mention each event calls 'this', which would make the logic use the wrong control if it were fired from a different one than it was intended).
To make things easy, it would be best to pass a string (representing the other text input id) to the event handler at the time it is created, and then calling the change() event manually on the second control. However, this puts things in an infinite loop of recursion. I thought of a way to get around the recursion, but it reqires a global variable.
Is there a better way than this, preferably one that doesn't require a global variable?
ml_inEvent = false;
$ctlToVal.bind('keyup change', {feedbackCtl: ml_feedback, controlsToMonitor: ary, validationGroup: this.validationGroup, controlToFire: ctlToFire}, function(event) {
// Other processing happens here...
if (event.data.controlToFire != '') {
var $controlToFire = $('#' + event.data.controlToFire);
if ($controlToFire.length) {
// Use a global variable to ensure this event is not fired again
// as a result of calling the other one
if (!ml_inEvent) {
ml_inEvent = true;
$controlToFire.change();
ml_inEvent = false;
}
}
}
});
You can use the extraParameters argument on .trigger() to break out, for example:
$ctlToVal.bind('keyup change', {feedbackCtl: ml_feedback, controlsToMonitor: ary, validationGroup: this.validationGroup, controlToFire: ctlToFire}, function(event, fire) {
// Other processing happens here...
if(fire !== false) $('#' + event.data.controlToFire).trigger('change', false);
});
You can give it a try here. What this does is the event handler callback not only receives the event object but also any other arguments you pass in, in this case we're just using a simple false and a !=== check this in important so undefined (the parameter not passed at all) still changes both controls. So for instance $("#control").change() would change both controls, but still not loop...you can test that here.

Resources