Leaflet conditionalLayer plugin map variable is undefined - javascript

I'm moving a project to webpack, splitting code into modules, but at some places i bump into a situation where plugin or some other code expects map to be a visible global variable. The following exception are thrown because map is not exposed to it:
onRemove: function() {
this._removeMarkers();
this.onMap = false;
map.off("moveend", this._update);
},
Uncaught ReferenceError: map is not defined
at NewClass.onRemove (leaflet.conditionalLayer.js:77)
at NewClass.removeLayer (Layer.js:185)
at NewClass._onInputClick (Control.Layers.js:378)
at HTMLInputElement.handler (DomEvent.js:79)
Currently i create the map variable in the bundle file (map.js in my case) which i load with the page, and import it in other modules like this
import { map } from './map.js';
All the leaflet code and leaflet plugins are imported as scripts without webpack. How can I share a map instance across all the scripts just like as it would be created without a module?
For now i worked around this problem and declared map as window.map in a module and it works, but it seems there is more convenient way of importing instead of using window object.

Looks like a bug in leaflet-conditionalLayer plugin.
Its onRemove method should either:
Accept a map argument, as shown in Leaflet tutorial for extending Controls:
onRemove: function(map) {
// (do something)
}
Or use this._map (instead of map) as suggested in (your?) issue https://github.com/Eclipse1979/leaflet-conditionalLayer/issues/6
The good news is that Leaflet class system should enable you to easily patch that plugin, without having to wait for the plugin author to fix this bug:
L.ConditionalMarkers.include({
onRemove: function (map) {
this._removeMarkers();
this.onMap = false;
map.off("moveend", this._update);
}
});

Related

Dynamically load ArcGIS online modeule

It is possible to dynamically decide which models to require()?
It takes a while to load a bunch of widgets, using the JavaScript require() function, so I want to be able to only include the ones I'm using in any given instance to the ones I'm using.
Currently, I do this:
var reqs="esri/Map", "esri/WebMap", ... "esri/widgets/Legend";
require(reqs, function(Map, WebMap, ... ,Legend,) {
...
It's easy enough to fill the reqs array, but how can I vary the names in the function?
Not sure in which situation you would want to dynamically change the imports at runtime as the code where they are used is most likely static. Do you mean changing the list of imports?
You can always change this
require(
["esri/Map", "esri/WebMap", "esri/widgets/Legend"],
function(Map, WebMap, Legend) {
// Source code using Map, WebMap and Legend
});
to the list of dependencies you need:
require(
["esri/Map", "esri/SceneView", "esri/layers/KMLLayer"],
function(Map, WebMap, KMLLayer) {
// Source code using Map, SceneView and KMLLayer
});
See the ArcGIS API for JavaScript samples that all have different require() statements.
If you meant lazy-loading to reduce initial startup time, there is a nice modular example using Webpack and TypeScript:
https://github.com/Esri/jsapi-resources/tree/master/4.x/webpack/demo
It imports/requires all ArcGIS API for JavaScript classes after the main UI is finished loading. This is essentially done by calling import / require inside a method:
https://github.com/Esri/jsapi-resources/blob/master/4.x/webpack/demo/src/widgets/App.tsx#L53
Came up with a solution. Not as elegant as one would like (i.e. having to spec the class names directly), but it works.
var reqs="esri/Map", "esri/WebMap", ... "esri/widgets/Legend";
require(app.reqs, function()
var Map, WebMap, MapView, SceneView, KMLLayer. ... Legend;
for (i=0;i<app.reqs.length;++i) {
key=app.reqs[i].match(/([^\/]+)$/i)[1];
if (key == "Map") Map=arguments[i];
else if (key == "WebMap") WebMap=arguments[i];
...
else if (key == "Legend") Legend=arguments[i];
}

JavaScript intercept module import

I have a SPA (in Aurelia / TypeScript but that should not matter) which uses SystemJS. Let's say it runs at http://spa:5000/app.
It sometimes loads JavaScript modules like waterservice/external.js on demand from an external URL like http://otherhost:5002/fetchmodule?moduleId=waterservice.external.js. I use SystemJS.import(url) to do this and it works fine.
But when this external module wants to import another module with a simple import { OtherClass } from './other-class'; this (comprehensiblely) does not work. When loaded by the SPA it looks at http://spa:5000/app/other-class.js. In this case I have to intercept the path/location to redirect it to http://otherhost:5002/fetchmodule?moduleId=other-class.js.
Note: The Typescript compilation for waterservice/external.ts works find because the typescript compiler can find ./other-class.ts easily. Obviously I cannot use an absolute URL for the import.
How can I intercept the module loading inside a module I am importing with SystemJS?
One approach I already tested is to add a mapping in the SystemJS configuration. If I import it like import { OtherClass } from 'other-class'; and add a mapping like "other-class": "http://otherhost:5002/fetchmodule?moduleId=other-class" it works. But if this approach is good, how can I add mapping dynamically at runtime?
Other approaches like a generic load url interception are welcome too.
Update
I tried to intercept SystemJS as suggest by artem like this
var systemLoader = SystemJS;
var defaultNormalize = systemLoader.normalize;
systemLoader.normalize = function(name, parentName) {
console.error("Intercepting", name, parentName);
return defaultNormalize(name, parentName);
}
This would normally not change anything but produce some console output to see what is going on. Unfortunately this seems to do change something as I get an error Uncaught (in promise) TypeError: this.has is not a function inside system.js.
Then I tried to add mappings with SystemJS.config({map: ...});. Surprisingly this function works incremental, so when I call it, it does not loose the already provided mappings. So I can do:
System.config({map: {
"other-class": `http://otherhost:5002/fetchModule?moduleId=other-class.js`
}});
This does not work with relative paths (those which start with . or ..) but if I put the shared ones in the root this works out.
I would still prefer to intercept the loading to be able to handle more scenarios but at the moment I have no idea which has function is missing in the above approach.
how can I add mapping dynamically at runtime?
AFAIK SystemJS can be configured at any time just by calling
SystemJS.config({ map: { additional-mappings-here ... }});
If it does not work for you, you can override loader.normalize and add your own mapping from module ids to URLs there. Something along these lines:
// assuming you have one global SystemJS instance
var loader = SystemJS;
var defaultNormalize = loader.normalize;
loader.normalize = function(name, parentName) {
if (parentName == 'your-external-module' && name == 'your-external-submodule') {
return Promise.resolve('your-submodule-url');
} else {
return defaultNormalize.call(loader, name, parentName);
}
}
I have no idea if this will work with typescript or not. Also, you will have to figure out what names exactly are passed to loader.normalize in your case.
Also, if you use systemjs builder to bundle your code, you will need to add that override to the loader used by builder (and that's whole another story).

How can google.maps.geometry be undefined?

I'm writing a JavaScript application using the Google Maps Javascript API v3 and RequireJS. I wrote a little wrapper for gmaps in order to get the dependencies right:
define('gmaps', ['goog!maps,3.9,packages:[geometry],language:de,other_params:sensor=false&channel=...&client=...'],
function(){
return window.google.maps;
});
This works perfectly fine in most of the cases, even after minifying the code using the optimizer. However, sometimes I get an error saying gmaps.geometry is undefined in a module that has gmaps as a dependency and tries to calculate a distance:
define(['gmaps'], function(gmaps) {
return {
...
calcDistance: function(target) {
var position = this.getPosition();
var distance = gmaps.geometry.spherical.computeDistanceBetween(
new gmaps.LatLng(position.latitude, position.longitude),
new gmaps.LatLng(target.latitude, target.longitude)
);
return (distance / 1000).toFixed(2);
}
}
});
This only happens if I try to execute calcDistance right after the page and the required data has loaded and only sometimes. I guess this is some problem with the async loading of gmaps, but I don't fully understand it. How can gmaps be defined but gmaps.geometry be undefined? Is there any way to fix this?
It seems you are not loading the correct library. Just append &libraries=geometry in the library url.
Check this
google.maps.geometry.spherical error
packages:[geometry] doesn't seem to work like I thought it would, so geometry wasn't loaded at all. Google seems to internally load geometry at some point, so my code for distance calculation worked in most cases. I fixed the problem by changing the define call of my gmaps module to:
define('gmaps', ['goog!maps,3.9,language:de,other_params:libraries=geometry&sensor=false&channel=...&client=...'],
function(){
return window.google.maps;
});
I know this is an old question, but in my situation I see that geometry libraries is not available at the initialization. I don't know why. But it became defined after some time. So I resolve the problem using a wait function.
setTimeout(waitForGeometryLibraries, 250);
function waitForGeometryLibraries(){
if(typeof google.maps.geometry !== "undefined"){
// code using geometry library
}
else{
setTimeout(waitForGeometryLibraries, 250);
}
}

How can I make requirejs polymorphically load a module?

I am currently developing a single page application using Cordova 3.4.0, Requirejs and Backbone. While porting my application from iPhone to iPad, I need to change some functions in some views and keep other parts intact.
To keep the change minimal, my solution is to create new object for each view I need to change, inherit all properties from original view and override only necessary functions.
To do so, I need to configure Requirejs so that in iPad, if I require, for instance, 'user/view/edit-profile.js', it will check whether there was a 'user/ipad/view/edit-profile.js' file, if there is one, requires it, otherwise require 'user/view/edit-profile.js'.
I have tried i18n, but it is not right for this situation. I am coming up with an idea of creating a new plugin for requirejs to do the task.
Does anyone have any suggestion for my problem?
Btw, Since the required file changes dynamically according to the platform. I call it polymorphism.
You could use path fallbacks:
paths: {
"user/view/edit-profile": ["user/ipad/view/edit-profile", "user/view/edit-profile"]
}
The above will make RequireJS try to load the ipad variant first. If as you develop your application you end up with logic to complex for fallbacks, you can use errbacks:
function onload(module) {
// Whatever you want to do...
};
require([module_a], onload, function (err) {
require([module_b], onload);
});
The code above will try to load a module from module_a and then from module_b. I use this kind of code to load modules with names that are computed at run time.
Since every module is a Backbone View, I come up with a solution, which will override extend function of Backbone View to return modified object depending on the existence of ipad, iphone or android dependencies.
The solution will require that if a base view have iPad version, it have to declare the iPad versions at dependencies, the extend function will extend existing view by the iPad view so that the iPad view can inherit all properties of base view and override only necessary functions.
I name the view PolyplatformView. Each view need to declare its ID, for example: user/view/edit-profile
Backbone.PolyplatformView = Backbone.View.extend({
});
Backbone.PolyplatformView.extend = function() {
if(arguments[0].moduleId) {
var extendPropsPath = arguments[0].moduleId.replace('/', '/' + constants.platform_name + '/');
// turn user/view/edit-profile -> user/ipad/view/edit-profile
if(require.defined(extendPropsPath)) {
_.extend(arguments[0], require(extendPropsPath));
}
} else {
console.warn('No module Id in polyplatform view -> cannot extend its parent');
}
var extended = Backbone.View.extend.apply(this, arguments);
return extended;
}

Require.js and reusable UI functions

I'm working on a project written using Require.js. There are a number of reused functions that are currently being called from the global scope. These functions involve ui transitions, hide/show, and general on hover events. I want to organize these functions right into require, but not quite sure where/how to include them.
For example let's say in the app there are multiple spots that may call a common function of showDropdown(). And let's say it requires jQuery for the animation. Where or how would be the best place to store the showDropdown function?
Say a simple function like:
function showDropdown(id) {
var thisdropdown = $(id).find('.dropdown');
$(thisdropdown).slideDown();
}
I could create a UI folder, with the different js functions all being their own file. Then simply require them on any other files that are dependent on them. But regardless, those files will need to export their function to the global scope to be accessible correct?
I feel there is an obvious answer/setup as this must be fairly common item.
In addition, I am writing this in a backbone app, but I don't believe that has any direct impact, more of a require.js question.
Create a util library or something like that:
// util.js
define({
showDropdown: function(id) {
var thisdropdown = $(id).find('.dropdown');
thisdropdown.slideDown();
}
});
Then use it elsewhere:
require(['util'], function(util) {
util.showDropdown('my-id');
});

Categories