gabesmed / ember-leaflet

Ember + Leaflet = Fun with maps
gabesmed.github.io/ember-leaflet
MIT License
164 stars 35 forks source link

icon binding howto #80

Open pedrokost opened 10 years ago

pedrokost commented 10 years ago

Hi,

I can't find documentation or examples on how to bind icons (reading through the issues shows that it should be possible now).

I have an Ember Leaflet map with markers of places (EmberLeaflet.MapView.extend), and next to it a list of places (Ember.Component.extend). What I want to accomplish is that when I click on a a place in the list, the corresponding marker changes icon/size/color.

There is also a controller, that contains the list of places, which is used by the map and the list.

How can I achieve this?

PS: I am fairly new to Ember

This is what I currently have:

I currently have several layers of indirection in order to display the markers, which means I've don't know how to access the markers from the controller


// The controller

export default Ember.ArrayController.extend({
  itemController: 'klub',
  hoveredKlub: null,
  hoveredMarker: null,
  layers: function(){
    return this.map(function(klub){
      return {
        location: klub.get('latlng'),
        id: klub.get('id'),
      };
    }.bind(this));
  }.property('@each.latlng'),
  actions: {
    highlightKlub: function(klubId) {
      var item = this.findBy('id', klubId);
      var marker = this.get('layers').findBy('id', klubId);

      // TODO
      // layer.setIcon(new HighlightedIcon())

      this.set('hoveredKlub', item);
      // this.set('hoveredMarker', marker)
    },
    unHighlightKlub: function() {
      this.set('hoveredKlub', null);
      // this.get('hoveredMarker').setIcon(new L.Icon.Default());  // TODO
      // this.set('hoveredMarker', null);
    }
  }
});

// MapView
export default EmberLeaflet.MapView.extend({
    center: L.latLng(46.2214969, 14.6),
    zoom: 9,
    options: {
      minZoom: 8,
      maxBounds: L.latLngBounds(southWest, northEast)
    },
    childLayers: [
      GoogleMapLayer,
      MarkerClusterLayer
    ],
    subscribeToKlubHovered: function() {
      window.pubsub.subscribe('klub.hovered', function(id){
        this.get('controller').send('highlightKlub', id);
      }.bind(this));

      window.pubsub.subscribe('klub.unhovered', function(){
        this.get('controller').send('unHighlightKlub');
      }.bind(this));
    }.on('didInsertElement'),
    unsubscribeFromKlubHovered: function () {
      window.pubsub.unsubscribe('klub.hovered');
      window.pubsub.unsubscribe('klub.unhovered');
    }.on('willDestroyElement')
  }
);

// MarkerClusterCollection

export default EmberLeaflet.ContainerLayer.extend({
  childLayers: [ MarkerCollectionLayer ],
  _newLayer: function() {
    return new L.MarkerClusterGroup();
  }
});

// MarkerCollectionLayer

export default EmberLeaflet.MarkerCollectionLayer.extend({
  contentBinding: 'controller.layers',
  itemLayerClass: KlubMarker
});

// KlubMarker

export default EmberLeaflet.MarkerLayer.extend({
    mouseover: function(e) {
      window.pubsub.publish('klub.hovered', e.target.content.id);
    },
    mouseout: function(e) {
      window.pubsub.publish('klub.unhovered');
    }
  }
);
miguelcobain commented 10 years ago

Basic approach:

export default EmberLeaflet.MarkerLayer.extend(EmberLeaflet.DraggableMixin,EmberLeaflet.PopupMixin, {

  _updateLayerOnIconChange: Ember.observer(function(){
    console.log('icon changed: update leaflet icon');
    var newIcon = get(this, 'icon');
    var oldIcon = this._layer && this._layer.options.icon;
    if(oldIcon && newIcon && oldIcon !== newIcon) {
      var draggable = this._layer.dragging.enabled();
      this._layer.setIcon(newIcon);
      if(draggable) this._layer.dragging.enable();
      else this._layer.dragging.disable();
    }
  }, 'icon'),

  icon : function(){
    //define here the logic for your icon
    //return a [Leaflet icon](http://leafletjs.com/reference.html#icon)
  }.property(/* You may define dependent keys here, just like a normal ember CP */)

});

_updateLayerOnIconChange deals with some known draggability problems for you. Refer to #58 Also, if you want to add this behavior to all MarkerLayer you should reopen instead of extending. This should be included in the project, but didn't have the time.

Hope this helps.

pedrokost commented 9 years ago

Thank you @miguelcobain for the reply!

Am I right that I only need _updateLayerOnIconChange if I use the DraggableMixin mixin? I don't need markes to be draggable.

Second, the icon CP is never called. Not even once. I have tried setting the icon property also in the item controller (from which the MarkerLayer takes the location) with the same result.

export default EmberLeaflet.MarkerLayer.extend(
  {
    icon: function(){
      console.log('will change icon');  // this is never printed
      return new L.Icon.Default();
    }.property('isHovered'),

    click: function() {
      this.get('controller').send('showKlub', this.content);
    },
    mouseover: function() {
      // This sets the item controller 'isHovered' to true
      console.log(this.get('content.isHovered'));
      this.content.send('hover', true);
    },
    mouseout: function() {
      console.log(this.content.get('isHovered'));
      // This sets the item controller 'isHovered' to false
      this.content.send('hover', false);
    }
  }
);
export default EmberLeaflet.MarkerCollectionLayer.extend({
  contentBinding: 'controller',
  itemLayerClass: KlubMarker
});
pedrokost commented 9 years ago

I just upgraded to the latest commit on the master branch (before, I was specifying ember-leaflet#0.6.0), but it still doesn't work.

miguelcobain commented 9 years ago

No, you need the _updateLayerOnIconChange even if your marker is not draggable. Sorry for not being explicit. That observer updates the icon, preserving the draggability. It is safe to use even if your marker isn't draggable.

Let me know how it goes with _updateLayerOnIconChange on your MarkerLayer subclass.

Edit:

Your icon CP is depending on isHovered. You must be sure that property is actually changed.

pedrokost commented 9 years ago

_updateLayerOnIconChange doesn't make it work.

isHovered is changed in the "klub controller" from which the markers take the location. You can see it change in the list on the right. Instead of pasting another code snipped, here's a link to MarkerLayer in the repo

Maybe you spot something I missed.

miguelcobain commented 9 years ago

I believe the problem is that your isHovered never really changes, thus never firing the icon observer. Something wrong with your controller binding.

Why don't you user your mouseover and mouseout handler on your marker to set isHovered on your marker? I don't see the need to delegate that to an action on a controller, but maybe I'm missing something.

If you still can't make this work, please set up a simple JSBin that I can fix up for you.

miguelcobain commented 9 years ago

I made a PR to your repository. I forgot some important details in my previous explanation.

pedrokost commented 9 years ago

Wow, thanks so much! There's no way I would have solved this without your knowledge.

There's one thing that bothers me still. The isHovered property seems to be local to the MarkerLayer, and not the one obtained from the ObjectController. In order to watch for changes in the controllers isHovered property, I had to change all references to it from the MarkeLayer to be content.isHovered, or alternatively provide an alias isHovered: Ember.computed.alias('content.isHovered').

This is confusing, because properties like location are automatically obtained from the controller, without the need to call content.location.

pedrokost commented 9 years ago

Why don't you user your mouseover and mouseout handler on your marker to set isHovered on your marker? I don't see the need to delegate that to an action on a controller, but maybe I'm missing something.

It's only because I have similar calls coming from the places list, and I though it was better that the markers and list know less about the item controller's property isHovered, and only need to know they can send the hover action. I don't think I won anything for this; as I said, I am only beginning with Ember and learning as I go along. I am eventually moving back to directly setting the method as you proposed.