Load multiple D3 maps with similar source code - javascript

I built a D3 map that highlights which counties voted for the new governor in my state. I want to build a similar map for another political race using the same code as my governor map. I copied the governor map code and modified it for the other race, changing the geojson file and where the svg will go. I put the other race's code into an external JS file and placed it below my governor JS file in my index.html. Now, depending which file is loaded first in my .html file, one map will appear while the other appears white. Is there anyway I can make both codes different enough to have the maps appear at the same time?

It appears you are writing to the global namespace (In your case, the window object).
When you make the json request through d3, the json file is loaded asynchronously, which means the rest of your javascript code will keep running. This also means that whichever file is being loaded second is overriding all of the variables you just declared, and by the time the json request is processed, the variables (that were declared by the first script) were already overridden by the second script, so each time the json is loaded, you're essentially writing to the same DOM element.
If you want a quick fix, you could just wrap each script in a function and call them onload or use DOMContentLoaded or jquery, but since they are so similar you could just use a single function such as:
function createPoliticalMap(elementID, jsonUrl){
// your code...
//...
var svg = d3.select(elementID).append("svg")
//...
d3.json(jsonUrl,function(error,geodata) {
//...
}
}
And then just do:
createPoliticalMap('#map-gov', 'data/gov.geojson');
createPoliticalMap('#map-ag', 'data/ag.geojson');
Or you could take advantage of JavaScript's object oriented capabilities to define a 'class' of sorts like:
function PoliticalMap(elementID, jsonUrl){
// define your stuff and make the json request based off of the parameters.
}
And do:
var govMap = new PoliticalMap('#map-gov', 'data/gov.geojson');
var agMap = new PoliticalMap('#map-ag', 'data/ag.geojson');
If you wanted to be able to manipulate the D3 graph after creation.
Then adding more political maps becomes trivial, and you could include the code more easily in other pages if you want.

Related

Source map for a dynamically created function

JavaScript allows you to create new functions at runtime with the new Function(...) or eval. Is it possible for a function created in this way to set something like a source map? That is, set the name of the source file from which it was allegedly loaded, as well as different source line numbers.
If there is no general solution, then there may be particular solutions for specific JavaScript engines.
For eval'd code you can simply add:
//# sourceMappingURL=<url>
//# sourceURL=<filename.js>
To the bottom of the code you are evaluating. For the sourceMappingURL, you will send in a data url, since naturally you don't have an existing source map hosted somewhere and you will be dynamically generating it.
Place your original source in the sourcesContent field in the source map, set the file field in the source map to whatever you set in sourceURL, and you should be good to go.
My current attempts at getting this to work in Function constructors is failing, which is a shame- in this case, in both Edge and Chrome, the original source is correctly listed, but it appears that the line number information is broken (the instruction pointer always points to the last line). I am currently investigating this. However, the source map spec does allude to Function constructors containing sourcemaps:
If the generated code is being evaluated as a string with the eval() function or via new Function(), then the source origin will be the page’s origin.
This leads me to believe either I am doing something silly, or there is not great support for this feature.

Creating and minifying JavaScript dynamically in ASP.NET MVC server-side code

I am using a ASP.NET route (to intercept the call to the .js) and controller to generate some JS I want to use on my client. The reason I'm doing this is so as to not have to duplicate id's or constants on the client. Here's the output of my JS:
app.serviceRootURL = 'http://localhost:65211/'; // set in my web.config
app.ajaxResponseStatuses = [
{ "status":"Success", "id":0 }, // set in my C# DTO
{ "status":"Failure", "id":1 },
];
First of all, I am not sure if this is the best approach, so other suggestions to doing this would be beneficial.
More importantly though, I'm wondering how I can bundle and minify this. As I understand it, even if I could minify the JS at compile or run-time, minification will change the names of my variables. So in the above JS, app.ajaxResponseStatuses could get changed to a.bc, and then in the actual JS files where I'm trying to access that variable, they could be looking for x.yz.
Can I minify this code and get it to the server?
Will I still be able to use the above properties in other minified files?
(bonus points) Is this a good aproach to pass server-side-only values to be used on the client?
Part 1
If you are generating the js at runtime, bundling isn't possible (at least not efficiently). You would have to create a new bundle for every request which isn't terribly quick. Plus, you wouldn't be able to cache the regular, constant script bundle.
EDIT: While bundling server-generated js isn't practical, rendering the values into a script tag in the page can achieve the same benefit of bundling, fewer HTTP calls. See the edit in Part 3 for more.
Minifying the server generated js however, is totally possible. This question should have the answer you're looking for. However, I'd recommend you cache this on the server if possible, as the minification process itself could take longer than simply sending down the extra bits.
Part 2
In most minifiers, global variables (those accessible on the window object) are skipped during the name mangling. With the same respect, variables that are accessed in other files that are not defined within that file are not renamed.
For example, if you have the following file...
// outside of a closure, so globally accessible
var foo = 1;
function bar() {
// within a closure, and defined with `var`, not globally accessible
var bar;
// reference to variable declared in another file
baz = null;
}
it would be minified as follows (with whitespace included for readability
var foo = 1;
function bar() {
var b;
baz = null;
}
This is one reason it is important to always declare your variables using the var keyword, otherwise they are assumed to be references to global variables and will not be minified.
Also, JSON (not Javascript object literals!!!) will never be distorted by minifiers, because it consists of string literals for all keys, and all values that aren't of another literal type.
Part 3
Not a bad way, and at my job we do use this approach. For small files though, or simple config values, we have transitioned to rendering server values in a script tag using ASP.NET in the actual view. i.e.
Default.aspx
<script> window.globals = <%= JsonConvert.SerializeObject(new AppGlobals(currentUser)) %>; </script>
We rip this out into a code behind, but the premise is the same.
EDIT:
Server-Generated JS (at it's own uri)
Pros
Cacheable by browser (if fresh values aren't needed on every request)
Cons
Extra round trip
Use when:
Your generated files are large, but rarely change or are the same for multiple users. These scripts can be treated the same as other static assets. To give an example, we serve a js file containing all the text in our app for localization purposes. We can serve a different js file based on the language set in the user's settings, but these values only change once at most with every release, so we can set aggressive cache headers and use a hash in the uri, along with a query string for the locale, to leverage browser caching and download each language file only once per client. Plus, if this file is going to be the same for every user accessing the same uri, you can cache it at the web server (IIS, Apache, etc.).
Ex: /api/language.v1-0-0.js?locale=en
Your js is independent from the rest of your app and not having it won't delay rendering. In this case, you can add the async attribute to your script tag, and this file will be downloaded asynchronously and executed when it is received without preventing the execution of other javascript.
Server-Rendered JS (within the page in a script tag)
Pros
No extra HTTP calls
Cons
Can add extra weight to your HTML, which may not be cacheable or minified depending on your circumstances
Use when:
Your values change often. The weight added to the page should be negligible unless you have a huge number of values (in that case, you might consider splitting them up and adding API endpoints for these values, and only getting them when you need them). With this, you can cut out the extra HTTP call as the js is injected into a script tag on a page the user would already have to retrieve.
But...
Don't waste too much time worrying about it. The differences in these two approaches is almost always negligible. If it becomes a problem, try both and use the better option for your case.

Javascript .js File Guidelines - How do I use a function outside of this file?

So two part question here. Basically, what is the proper practise for javascript function locations? I assumed it would be to have several MyScriptFile.js files, each with a few functions instead of one huge AllMyScripts.js file, as not every page needs every function.
However I'm not sure how to reference another function from outside of this file...
My situation: I'm using an AJAX request in many of my pages. Each request response is different (drawn from different files, etc) and is very hard to make dynamic (one-script-fits-all would be difficult). However, I do have my MakeAJAXRequest() function which creates the request object, which is standard to all DoSomethingWithRequest() functions.
How do I include MakeAJAXRequest() in the other files which contain the DoSomethingWithRequest() functions? It seems as though I should have been able to find this.. but I havn't come across it.
tl;dr I have MakeObject.js and UseObject.js. How does UseObject() reference MakeObject()?
EDIT: Found that if you include MakeObject BEFORE UseObject in your HTML <script> tags, UseObject will be able to reference MakeObject. Seems a little dirty still, as anybody who wants to use the UseObject script will have to be aware of the MakeObject dependency...
If you want to ensure your dependencies are loaded, you could use a function such as this: http://phpjs.org/functions/include:433 There is also include_once(), require(), and require_once()

How to generate the right URLs in javascript when externalizing scripts

I am working on an ASP.NET MVC3 app. I was reading about good Javascript practices in various places and decided it would be best if I externalized the javascript and jQuery calls that were sitting in a <script/> tag at the bottom of my views.
I put each view's Javascript in a separate file with a standardized naming convention so I could generate the <script/> tag to include the appropriate file in my layout view.
But it's on the verge of being too complicated to be maintainable, and I have run into one problem that breaks the whole scheme, namely the need to occasionally insert URLs into the javascript.
For example: if I'm doing a $.getJSON call, I need to provide a URL. I've been generating them on the server side, in the views, using the UrlHelper class, like so:
var _RegistrationSelectClassUrl = '#Url.Action("SelectClass", "Student", new { area = "Registration", id = Model.Person.PersonID })';
And this worked nicely ... up to the point where I externalized the javascript. No longer being part of views, calls to UrlHelper were just strings again.
So my question is this: if I stick with the externalized javascript, I need a way to insert or generate the appropriate URLs. Other than sticking a script section in the layout file that uses UrlHelper to define a constant for every URL I might want to use, I can't think of a good one.
Any suggestions?
External JS:
var namespace = {
init: function (url) {
this._RegistrationSelectClassUrl = url;
},
_RegistrationSelectClassUrl: ''
};
View:
<script src="external/js.js" />
<script>
namespace.init('#Url.Action("SelectClass", "Student", new { area = "Registration", id = Model.Person.PersonID })');
</script>
This is a basic example.
You can't. The moment the script leaves the parsed page it loses all access to the model. If you added this code into a PartialView, you would be able to parse things with the Helper classes, but since your link is dependent on the model, your scripts will have to be generated in the page itself.
A way to externalize it might be to have a mapped array of links that is populated by the model view itself. You could then add link text into this array mapping based on some key that would then be accessible by the external script after load. I would recommend not using the global variables for this and prototype an object for this purpose.
Some documentation for this: http://www.javascripttoolbox.com/bestpractices/#namespace

How do I dynamically add points to a Google Map when the bounds change?

I can currently place static points in the map window:
var latlng = new GLatLng(lat, lng);
map.addOverlay(new GMarker(latlng, markerOptions));
And I know how to get the bounds of the window. I can also code a page that will take the bounds and return the points that are inside them in any format.
The problems I have now:
Does the moveend event capture all moves, including zoom changes? Or do I need to watch for that event separately?
How should I call my external datasource? (should I just do a JQuery $.get request?)
What should that datasource return?
How do I display that data on the map?
I'm using this in one of my sites. I think it's exactly what you are looking for. But be warned "gmaps-utility-library" plugins have some buggy/bad code so it's a good idea to go over it and double check if everything is working as it should (I haven't encountered any bugs in marker manager but in some other plugins from that library).
Here's reference and examples.
Even if you want to code your own this one is probably a good starting point.
EDIT
My answer for 3-4:
It really depends on situation. If it's static (you just need to manage a lot points on map > 300) then you could serve all points together with the page where the map is located in (as JavaScript array for example). If user interacts with data then probably it's better to use AJAX. If you use jQuery (or any other JS library) in your site then use ajax function from that library if not then go with the one which comes from gmaps. It's because it's nice to use same AJAX function in whole site instead of using 2 which does the same job.
If you are taking AJAX path you have 2 options:
Load all markers for whole map in one request.
Load markers that shows up on user screen + small margin.
If you expect that user wants to see the big picture or that he will want to see all/most of the points then go for option 1 + marker manager (like the one I recommended or your own similar).
If there's really a lot of points and user will never be interested in most of them then go for option 2 + manager or just this simple algorithm: Clear map -> Request points for map window bounds + magin / (Cache) -> Draw points -> Repeat for each move/zoom.
From personal experience (I've used both AJAX options) marker manager does quite nice job, can handle a lot points and overall user experience is a lot smoother then loading points for just viewport. Requesting new points and drawing them on map is quite laggy/choppy. Of course it depends on amount of points.
Here is a great example of making external calls to a data source from google:
Google Maps PHP and SQL
If you're storing the points in just an external javascript file then I would recommend using JSON format over XML as the XML parser that google maps uses is much slower than json.
Moveend yes captures any kind of changes.
If you're using only an external XML file then you can use google's function to call and download the XML file (seen in the link above), otherwise I would create a file that can be updated, and parse with json.
I believe it should return a JSON as i've discovered it is much quicker than XML parsing.
You would parse through the array and create a marker with each of these. Here is a good example of using json:
Jquery and Google Maps
Good Luck!
I'm currently doing something very similar to this, along with clustering on the server.
Yes, moveend will capture zoom changes.
If you're already using jQuery, a $.get request would work great.
I have tried returning both XML and JSON. These worked fine, but I discovered that parsing through these data formats caused the application to get significantly slower as more points were added to the database. I now return plain Javascript which I execute with eval(). I don't have a problem with this since I completely trust the source of the Javascript (me).
The Javascript function which adds a marker contains only the latitude, longitude, and id of the marker. The page already contains a Javascript array with all of the data for each marker, which can be accessed when the marker is clicked on.
That's how I'm doing it, but you could certainly accompish what you want to do with a number of methods.

Categories

Resources