The problem is that behaviors get attached multiple times if we load maps via ajax, in an edit form for example (with #1273802: Geofield/Openlayers widget refactor). And if we have something like geolocation enabled, it reset all maps, not just the new one. This patch fixes that.

I know the patch is huge, the only meaningful change is this :

var data = $(context).data('openlayers');
if (data && data.map.behaviors['openlayers_behavior_CURRENTFEATURE']) {

in all includes/behaviors/js/*.js files is changed to

var data = $(context).data('openlayers'),
    behavior = data && data.map.behaviors['openlayers_behavior_CURRENTFEATURE'];
if (!$(context).hasClass('openlayers-CURRENTFEATURE-processed') && behavior) {

  // function code

  $(context).addClass('openlayers-CURENTFEATURE-processed');
}

I could have used jquery().once() and it'd have done the same thing, but the changes would be a bit more intrusive.

I also replaced all occurrence of data.map.behaviors['openlayers_behavior_CURRENTFEATURE'] into behavior, only inside the main if.

It was all handcrafted with love so there is no find/replace nonsense. I created a map with all behaviors enabled, worked fine and as designed.

The vast majority is whitespace change. I indented all the files the same way so it feels more coherent and added missing colons. That's what's making this patch huge.

Comments

zzolo’s picture

@nod_. Thanks so much for the patch. I don't have that much time to test this. Maybe someone else can come by and just confirm that this is good. A quick glance says ok.

A minor coding issue that is not even close to a blocker, but I really don't like this way of decalring variables in JS:

var x = y,
  a = b;

Besides being ugly, it leaves more room for not using var when it would change the scope of the variable drastically.

var x = y;
var a = b;
zzolo’s picture

Hey, here's a thought related to this. If we are going to add more logic around every single behavior, maybe we should be abstracting this out more. Here is a real quick example of making a generic openlayers.addBehavior method to handle the same logic:

// Add behavior method for OpenLayers
Drupal.openlayers.addBehavior = function(behavior, attach, detach) {
  Drupal.behaviors[behavior].attach = function(context, settings) {
    var data = $(context).data('openlayers');
    var localBehavior = data.map.behaviors[behavior];
    var class = 'openlayers-' + behavior + '-processed';
    if (data && localBehavior) {
      if (!$(context).hasClass(class)) {
        $(context).addClass(class);
        attach(data, localBehavior, context, settings);
      }
    }
  };
}

// New layer implementation
Drupal.openlayers.addBehavior('openlayers_behavior_attribution',
  function(data, behavior, context, settings) {
    var control = new OpenLayers.Control.Attribution();
    data.openlayers.addControl(control);
    control.activate();    
  });
nod_’s picture

I wanted to change it again once all the whitespace thing got commited to use jquery once().

var localBehavior = 'xxx';
Drupal.behaviors['openlayers_behavior_' + localBehavior] = {
  attach: function (context, settings) {
    var data = $(context).data('openlayers');
    // you need data && or if will crash JS if data is empty
    var behavior = data && data.map.behaviors['openlayers_behavior_' + localBehavior];
    if (localBehavior) {
      $(context).once(localBehavior, function () {
        // func
      });
    }
  }
};

Abstracting away works too, but i'm not even sure that if (localBehavior) is even needed. I mean there are quite a lot of behaviors that don't even use the variable. so it could come down to :

// Add behavior method for OpenLayers
var localBehavior = 'xxx';
Drupal.behaviors['openlayers_behavior_' + localBehavior] = {
  attach: function (context, settings) {
    var data = $(context).data('openlayers');
    $(context).once(localBehavior, function () {
      // func
    });
  }
};

The problem could be if you have different maps with different settings on the same page.

But yeah I guess in the end abstracting is the way to go to shorten the code. I can work on a patch on top of the other one if you'd like.

nod_’s picture

updated patch. abstracted the thing like you said zzolo. it's so much better and shorter now.

Drupal.openlayers.addBehavior('navigation', function (data, behavior) {
  var options = {
    'zoomWheelEnabled': behavior.zoomWheelEnabled,
    'documentDrag': !!behavior.documentDrag
  };
  Drupal.openlayers.addControl(data.openlayers, 'Navigation', options);
});

and

(function($) {
  /**
   * Set default values and check that the behavior is applied once
   */
  Drupal.openlayers.addBehavior = function (behavior, attach, detach) {
    Drupal.behaviors['openlayers_behavior_' + behavior] = {
      attach: function (context, settings) {
        var data = $(context).data('openlayers');
        // need "data &&" to avoid js crash when data is empty
        var localBehavior = data && data.map.behaviors['openlayers_behavior_' + behavior];
        var that = this; // fix scope in attach
        if (localBehavior) {
          $(context).once('openlayers-' + behavior, function () {
            attach.apply(that, [data, localBehavior, context, settings]);
          });
        }
      },
      detach: detach
    };
  };
  
  /**
   * Adding a control is even shorter
   */
  Drupal.openlayers.addControl = function (openlayers, controlName, options) {
    var control = new OpenLayers.Control[controlName](options);
    openlayers.addControl(control);
    control.activate();
    return control;
  };
}(jQuery));

It's against 7.x-2.x of today i fixed a couple of leaking vars too.

the abstraction above is in a new file that i include in openlayers.render.inc with a simple

function _openlayers_behaviors_render($behaviors = array(), &$map = array()) {
  drupal_add_js(drupal_get_path('module', 'openlayers') . '/includes/behaviors/js/openlayers_behavior.js');
  // bla bla
}

It's not in the patch as i didn't know if it was the right place to add it.

Status: Needs review » Needs work

The last submitted patch, openlayers-fix-behavior-execution-1329560-4.patch, failed testing.

nod_’s picture

Status: Needs work » Needs review
StatusFileSize
new41.02 KB

happy now Mr. Bot ? :þ

zzolo’s picture

Overall, this looks good. A couple minor things. I would put the helper functions in the main openlayers.js file instead of a separate one.

I would still love it if someone else could test this out given that it is a large change and affects lots of code.

zzolo’s picture

Status: Needs review » Fixed

Hey, so I applied this patch (after cleaning it up a bit) specifically because I moved all the plugins into the appropriate place for better use of Ctools. Please test out.

nod_’s picture

Awesome, thanks :)

A little something got forgotten on the way

zzolo’s picture

Patched. Many thanks.

For others' reference, more abstract conversation happening here: #1087572: Exposed filters with D7 OpenLayers Data displays

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.