I've been working on a site that loads a lot of panels using AJAX, and I've run into a bunch of problems involving attaching Geofield Map behaviors because of how the Google Maps API loads. There have also been some problems opening a print dialog box in a printable node view because it opens before the map tiles arrive.

We also need to be able to get at the map instances that are created so that we can adjust their heights in a column layout.

I've modified the geofield_map.js file to handle some of these issues, and I would welcome some discussion about the approach I took and whether anyone would find this useful or want to add something like this to the module.

The changes involve removing Google Maps JS from the Drupal JS array, and instead loading it during attachBehaviors with a callback. All of the attachBehaviors calls are queued, and then run after the API loads in the callback sent. There are also two events that are triggered; one for when the API loads, and another for each map that's created so other modules' scripts can interact with the maps.

CommentFileSizeAuthor
#1 javascript_async-1992184-1.patch4.5 KBkarlshea

Comments

karlshea’s picture

StatusFileSize
new4.5 KB

Attached is the patch.

karlshea’s picture

I'll also add that one of the reasons I had to load the Maps API right in the module's JS file is that attaching a callback parameter to the Maps URL changes the script that's loaded from Google.

I was getting errors from within the Google script because it was loaded before document.body was available so it couldn't attach the script element it was trying to insert.

technicalknockout’s picture

I've also had some issues with ajax + geofield. I'm programmatically adding a map view within a multi-step webform and reloading the location search results on the map via ajax. The issue I experienced was that the ajax call ran through all the drupal_add_js call again and was causing some js settings to be set as arrays, rather than single values. This cause google maps api to choke up on the options.

I didn't test the patch above, but I think it's a good approach. The thing that worries me is that each contrib module will end up creating its own sub-mechanisms of event handling and async loading. At least it's important to keep the conversation going about making sure contrib modules' javascript get ironed out to work in these different situations.

Unfortunately, https://drupal.org/node/1542344 looks like is will not be a thing in core until 9.x. In the meantime I think we just need more javascript developers putting more hands on deck on other solutions... maybe look into https://drupal.org/project/requirejs or https://drupal.org/project/async_js

Anyways, for my issue I hacked this module's js file for my purposes. I wouldn't suggest patching this module, but I'm including the code in case somebody else could use:

@@ -19,6 +19,10 @@
         // Checking to see if google variable exists. We need this b/c views breaks this sometimes. Probably
         // an AJAX/external javascript bug in core or something.
         if (typeof google != 'undefined' && typeof google.maps.ZoomControlStyle != 'undefined' && data != undefined) {
+          // data is buggy in ajax and creating array
+          if ('[object Array]' === (Object.prototype.toString.call(data.type))) {
+            data.type = data.type[0];
+          }
           var features = GeoJSON(data);
           // controltype
           var controltype = map_settings.controltype;
@@ -59,6 +63,16 @@
             scaleControl: (map_settings.scale ? true : false),
             scaleControlOptions: {style: google.maps.ScaleControlStyle.DEFAULT}
           };
+          // get rid of buggy arrays on ajax calls
+          for (prop in myOptions) {
+            if ("mapTypeId" === prop) {
+              myOptions[prop] = "roadmap";
+            }
+            if ('[object Array]' === (Object.prototype.toString.call(myOptions[prop]))) {
+              var newVal = myOptions[prop][ myOptions[prop].length - 1];
+              myOptions[prop] = newVal;
+            }
+          }

           var map = new google.maps.Map($(element).get(0), myOptions);
youngelpaso’s picture

I think you've hit the nail on the head about the map instance. We should be able to access it. I think instead of defining a new variable 'map' and instantiating the Google Map there in the closure we should make the map accessible so we can use the API, by doing something like: Drupal.geofield.map['instanceX'] = new google.maps.Map ....

That would make it easier to handle all sorts of things via AJAX and JS. Just my 2 cents. cheers