14

A number of the classes in Google Maps API v3 can be extended, specifically google.maps.MVCObject and google.maps.OverlayView.

In some of the examples, they will extend a class in the callback function initMap. My application is more robust than those examples and would prefer not to define a bunch of classes in the callback function.

Is the solution to (A) include Google Maps API before my own script and not include a callback function? Or (B) do I just define everything in the callback function? Or (C) some other approach.

Option A

<script src="https://maps.googleapis.com/maps/api/js?key=API_KEY"></script>
<script src="./assets/js/main.js" type="module"></script>

Option B

<script src="./assets/js/main.js" type="module"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=API_KEY&callback=initMap"></script>

Where initMap is in main.js and looks something like this:

function initMap() {

  class Alpha extends google.maps.MVCObject {}
  class Bravo extends google.maps.MVCObject {}
  class Charlie extends google.maps.MVCObject {}
  // More classes.
  class Zulu extends google.maps.MVCObject {}

  // Rest of code.

}

Option C

Some other approach.

hungerstar
  • 21,206
  • 6
  • 50
  • 59
  • The Google Maps JavaScript API is loading asynchronously, I think the callback approach is the most appropriate for async nature of Maps JavaScript API. – xomena Nov 17 '17 at 12:31
  • I'm not totally against using a callback. I'm just trying to avoid having 600 lines of class definitions in my callback. Thanks for the feedback. – hungerstar Nov 17 '17 at 20:01

3 Answers3

4

The documentation describes the following way top extend maps classes:

The MVCObject constructor is guaranteed to be an empty function, and so you may inherit from MVCObject by simply writing MySubclass.prototype = new google.maps.MVCObject();

And

Inherit from this class by setting your overlay's prototype: MyOverlay.prototype = new google.maps.OverlayView();. The OverlayView constructor is guaranteed to be an empty function.

So the (one possible) option C would be to define your classes separately and then only configure the inheritance inside the initMap:

function initMap() {

  Alpha.prototype = new google.maps.MVCObject();
  Bravo.prototype = new google.maps.MVCObject();
  ...
}

Or, even better, to keep everything together, you can have some bootstrap function inside your library file, so in the initMap you would just do this:

// in my_library.js:
// For now we don't mention that our class extends MVCObject
function Alpha() {
    console.log('Constructed Alpha');
    this.my_method = function() {
       // the `parent_method` can be defined in the
       // prototype we assign later
       this.parent_method();
    }
}

function Bravo() {
    console.log('Constructed Alpha');
}    

// The function to dynamically subclass our classes.
function init() {
   Alpha.prototype = new google.maps.MVCObject();
   Bravo.prototype = new google.maps.MVCObject();
}

// The callback for google maps script.
function initMap() {
    // invoke the `init` from my_library.
    my_library.init();;
}

The above uses instance methods (we define Alpha methods inside the constructor), alternatively we could define the constructor without methods, immediately create the instance and define the methods on it:

function Alpha() {
    console.log('Constructed Alpha');
}

var alpha = new Alpha();
alpha.my_method = function() {
   this.parent_method();
}

// The function to dynamically subclass our classes.
function init() {
   alpha.prototype = new google.maps.MVCObject();
}

To create more Alpha instances, we can clone the existing alpha object.

One more alternative is to define own object using the prototype and then use Alpha.prototype.prototype = MVCObject construct:

function Alpha() {
    console.log('Constructed Alpha');
}

Alpha.prototype.my_method = function() {
   this.parent_method();
}

// The function to dynamically subclass our classes.
function init() {
   // We can not overwrite Alpha.prototype as we will loose
   // the methods we defined, so we assign the prototype of
   // the prototype
   Alpha.prototype.prototype = new google.maps.MVCObject();
}
Borys Serebrov
  • 15,636
  • 2
  • 38
  • 54
  • Thanks for the reply. My main issue is not bloating the `initMap` definition with class definitions along with ensuring that the Google Maps API is available to be extended. If I use the API in async, `initMap` needs to be defined before the API is loaded. Both of your suggestions appear to Option B above. – hungerstar Nov 27 '17 at 20:40
  • @hungerstar Your class definitions have to go _somewhere_, and they will be the same amount of code wherever you put them. By putting them all in a function like Boris's `my_library.init()` you can make just a single call to that function, either from your `initMap()` if you're loading the API asynchronously, or elsewhere if you load the API synchronously. I don't see how adding one line of code to `initMap()` "bloats" the function. – Michael Geary Nov 27 '17 at 20:53
  • @MichaelGeary you're correct that placing class definitions in an `init` function, in fact, does not bloat the `initMap` callback. I was looking for a _"preferred"_ or _"typical"_ approach for Google Maps API applications. Having a bunch of class definitions in the `initMap` function seems like a bit of _"code smell."_ Perhaps it is not and I need to get over that, :P. Seems like the answer is pointing to how I want to organize my code and how I load the scripts vs a _"Google Maps API way"._ I am starting to warm up to wrapping all the class definitions in an `init` function. – hungerstar Nov 27 '17 at 23:15
  • @hungerstar the `Alpha.prototype = new google.maps.MVCObject()` is a way to say "Alpha is an MVCOjbect subclass". So the idea is that you have the complete class declaration somwhere, outside of any functions and then you only have these `X.prototype = new google.maps.BaseObject` inside your `mylibrary.init` function which you can call from `initMap`. So all your code stays out of initMap and also you don't write your classes inside any function, `init` only binds your classes to base objects in google maps library when everything is loaded. – Borys Serebrov Nov 28 '17 at 09:58
  • If I'm subclassing `MVCObject` outside of any functions then I have to do it after the API loads as `MVCObject` would be `undefined`. Basically, a synchronous approach. If I'm using `async` or loading my code before the API my class definitions do need to be in a function to get around `MVCObject` not being defined, correct? My take away from your answer is that instead of defining my subclasses in `initMap`, do it in another function and call that other function from `initMap`. – hungerstar Nov 28 '17 at 14:43
  • No, you define your classes out of any functions, they will be exactly same as you have them, the only difference is that they will not be "subclassed" yet. Once everything is loaded you do the "subclassing" dynamically by assigning the prototype (only the `Class.prototype = new google.maps.Base` calls go into the `init` function). – Borys Serebrov Nov 28 '17 at 16:26
  • I updated the code example, maybe it was not clear enough. – Borys Serebrov Nov 28 '17 at 16:43
  • Your updated code is what I initially understood from your answer. Maybe I'm missing something obvious, but I'm going to have methods on my subclasses. Assigning the prototype in `init` using `new` wouldn't allow me to define methods ahead of time. Would I then need to merge the two classes somehow? Thanks for all the input. – hungerstar Nov 29 '17 at 14:48
  • "Assigning the prototype in init using new wouldn't allow me to define methods ahead of time" - why not? All your code in your classes will stay the same regardless of the way you do the subclassing. What's important is that when your classes will actually be used, they will already be configured in `init` (which is OK since your code anyway can't work before google library is loaded). Maybe it would be clearer If you provided some minimal working example of your class, so we could modify it to use dynamic prototypes and see that it still works. – Borys Serebrov Nov 29 '17 at 15:17
  • I would assign my methods to the prototype, i.e. `Alpha.prototype.hello = function () {};`. Redefining the prototype would remove those methods. Are you talking about instance methods? i.e. `function Alpha() { this.hello = function () {}; }`. [This JSFiddle demonstrates what I'm expecting and doing](https://jsfiddle.net/nwd2nnwt/1). `var b` no longer has the `hello` method that was originally on `Alpha`. – hungerstar Dec 06 '17 at 16:38
  • Would my constructor for `Alpha` be [something like this](https://jsfiddle.net/nwd2nnwt/2/)? – hungerstar Dec 06 '17 at 16:47
  • You can either a) use `Alpha.prototype.prototype = new MVCObject` (https://jsfiddle.net/L28bkbo8/) or b) use instance methods (https://jsfiddle.net/gdgh6w6x/1/) – Borys Serebrov Dec 07 '17 at 11:24
  • @BorisSerebrov thanks for all the great feedback and patience! I think I'm going to lean on the instance methods solution as I'll be creating less than a hundred instances. Any drawbacks to the prototype.prototype approach and how would one do the instancing approach with ES6 classes ([my attempt](https://jsfiddle.net/gdgh6w6x/6/))? – hungerstar Dec 22 '17 at 22:44
  • The `class` syntax is only a syntactical sugar and prototypes are still working under the hood (that's why I prefer the old syntax). The `prototype` usage seems to be a bit different in ES6 and your example [works](https://jsfiddle.net/1rvhkqqk/2/) with `Alpha.prototype.__proto__ = Beta.prototype`. Also take a look at the documentation - [classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes), check also docs and examples for Object.setPrototypeOf and Object.create, maybe you'll find even better way to build your objects dynamically. – Borys Serebrov Dec 23 '17 at 00:40
  • I've tried `Object.create` and it didn't seem to work. I think I'll just have to use the ES5 syntax. Thanks again. – hungerstar Dec 26 '17 at 14:35
1

You can use version A and later in your code you can append the initMap callback in your main.js file. IN this way you'll have to use ajax calls to apply yout callback function.

Otherwise you'll have to use option B from the start, and define the initMap function in your main.js file.

You should also load the google maps api in async mode:

<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY" async defer></script>

Documentation and example: https://developers.google.com/maps/documentation/javascript/examples/map-simple

Pascut
  • 3,291
  • 6
  • 36
  • 64
  • I forgot to mention that I am loading all JS before the closing body tag so using/not using async shouldn't be an issue. I'm assuming the suggestion of ajax for Option A would be when in async mode as it would be unnecessary when not. If so, how would this work? – hungerstar Nov 27 '17 at 22:25
  • Even if the js is loaded in the footer is good to use async, because your page won't wait for the google maps api library to load. Yes, I suggest you to use ajax calls because I'm doing the same and it works well. An d you should use async and defer, here's why: https://stackoverflow.com/a/39711009/1564840 – Pascut Nov 28 '17 at 08:51
  • I understand how async and defer work. The page has already been loaded /rendered so there's likely only a microscopic improvement when using async in the footer. To reference your link the waterfall would be a green bar, then a blue bar, then a red bar when loaded in the footer. The link illustrates a waterfall for scripts loaded in the head or mid page. I'm primarily curious how your ajax suggestion works. – hungerstar Nov 28 '17 at 14:37
0

in separate, say, my_lib.js file ( it should be declared after '...maps.googleapis.com/..etc' string within < head > tag of your html page ) :

class MyPolygon extends google.maps.Polygon{ 
    field1;field2; map
    constructor(latlngs,bcolor,idLcl, id_of_start,id_of_finish,map ) {
        super({
        paths: latlngs,
        strokeColor: ''+bcolor,
        strokeOpacity: 0.8,
        strokeWeight: 2,
        suppressUndo: true,
        fillColor: '',
        fillOpacity: 0.35,
        my_id: idLcl,
        my_color: ''+bcolor,
        addrs_ids: [],
        start_id: id_of_start,
        finish_id:  id_of_finish,});
      this.map=map
      this.setMap(map)

   }
    func1(aParam){
       this.field1=0 ; this.field2=null
   }
}
class SomeOtherPlaceConsumableClass(){}

and in your html withinin javascript tag section you have,e.g. :

idLcl = idPrm
polygon=new MyPolygon(latlngs,color,idLcl, id_of_start,id_of_finish,map)

You can do that with any other Map API class

CodeToLife
  • 3,672
  • 2
  • 41
  • 29
  • This doesn't address the main question, which is, A) is it better to include ALL application code in a file before Google Maps code or B) include all application code in a single function after the Google Maps code. And explain, i.e. what are the pros and cons of the suggested approach. – hungerstar Dec 11 '20 at 15:28
  • @hungerstar ,the advantag is : you can make your html page readable for people and bringout/devide your logic. The disadvantage is when api, say , 'polygoncomplete' event in listener , creates internally a polygon object as a parameter, you have to wrap it with your custom class or to recreate a new one(yours) and copy properties of generated polygon to yours. Aka a type cast . The only this I think. – CodeToLife Dec 12 '20 at 08:33
  • @hungerstar the best way is to use e.g. `myPolygon =Object.assign(myPolygon, polygonPrm)` and destroy the polygonPrm object aftewards – CodeToLife Dec 19 '20 at 09:22