Take the 2-minute tour ×
Drupal Answers is a question and answer site for Drupal developers and administrators. It's 100% free, no registration required.

I have leaflet map attached to my form, this is the container div I used:

$form['general']['map'] = array(
  '#markup' => '<div class="map-wrapper" id="ngmap"></div>',
);

Everything works fine and map's being loaded. I also added an image upload functionality in bottom of my form like this:

$form['detailed']['img'] = array(
  '#type' => 'managed_file',
  '#upload_location' => 'public://map-images/',
  '#required' => TRUE,
  '#title' => 'Image',
  '#default_value' => $complain['image'],

);

When I click on ajax upload button after selecting the image file, it throws an error and nothings happen:

Uncaught Error: Map container is already initialized.

Seems like uploading file is forcing leaflet.js to reload map container. My guess is ajax is reloading and rendering the whole form all over again.

How's that possible and what's the easiest fix for this? Thank you.

Edit: How I added initializing function, look like this attached js is being recalled everytime I click on upload image ajax button:

(function($, Drupal) {

  // Custom functions and variables can go here
  Drupal.behaviors.moduleName = {
    attach:function() {
      map = new L.Map('ngmap', {center: new L.LatLng(37.472, 57.327), zoom: 13});
      googleLayer = new L.Google('HYBRID');
      map.addLayer(googleLayer);
    }
  };
}(jQuery, Drupal));
share|improve this question

2 Answers 2

up vote 2 down vote accepted

Drupal calls every declared behaviors when an AJAX call is performed. This is done to apply the declared behaviors to new loaded content via AJAX.

When a behavior is applied to an element a class is used to mark that element as behavior processed, so next behavior call ignore this element. The recommended way to do this is using the jQuery Once, included in Drupal 7. See the behaviors section of this page.

In your case you should use something like this:

(function($) {

  // Custom functions and variables can go here
  Drupal.behaviors.moduleName = {
    attach:function(context, settings) {
      $('#ngmap', context).once('leaflet', function () {
        map = new L.Map('ngmap', {center: new L.LatLng(37.472, 57.327), zoom: 13});
        googleLayer = new L.Google('HYBRID');
        map.addLayer(googleLayer);
      });
    }
  };
}(jQuery));

Once will add a 'leaflet-processed' class to the #ngmap element, so next time the behavior is called the initialized map is ignored.

Imagine you have several maps in the same page, and imagine you can load more by using AJAX calls. The above code, with some modifications, allows you to initialize every new loaded map after the AJAX call.

So, your fix works but is not the right way to do it (in general inline code is discouraged), I recommend you to use the #attached and Once approach.

share|improve this answer
    
This is a much better solution to my quick dirty fix. Thank you. –  Pooya Sanooei Aug 16 at 11:30

I initialized Map in an external js file and attached it to form using ['#attached'] property of Form API.

What I didn't know and the problem was that every time an ajax call happens Drupal recalls all attached js files, so it re-init leaflet map and throws that error (since it's already been init).

attached syntax (see Form API):

$form['#attached']['js'] = array(
  drupal_get_path('module', 'mymodule') . '/mymodule.js',
);

My quickfix for now was moving init code to inline using drupal_add_js() function, so it gets called only once. Hope this could help others too.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.