Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create an ember property dependent on a normal variable, function or logic?

The problem

I'm working on a leaflet view for emberjs, and I'm having some problems. Leaflet is an external library and somewhat irrelevant to the question, but just know that it is a mapping library.

Consider a simple property like zoom level. Leaflet map instances have a zoom level accessible through map.getZoom() and assignable through map.setZoom(zoomLevel). Also, users can interact with the map, and change its zoom level. Leaflet allows us to register a callback when the zoom changes.

I would like my "Ember-Leaflet" View to have a zoomLevel ember property. This way I can benefit from the ember object model (bind zoomLevel to a template or to another value, for example), and we're doing it "the ember way".

Base solution

What I currently have is a subclass of Ember.View, with a zoomLevel property. On didInsertElement I create the Leaflet map instance. The code is commented and should be self-explanatory.

App.Leaflet = Ember.View.extend({
    classNames : ['ember-leaflet'],

    //default zoom level
    zoomLevel : 13,
    didInsertElement : function() {
        var self = this;
        var zoomLevel = this.get('zoomLevel');

        // create map instance
        var map = L.map(this.$().get(0)).setView(center, zoomLevel);

        // configure map instance...

        // Event listeners
        map.on('zoomend', function(e) {
            self.set('zoomLevel', e.target.getZoom());
        });

        // save map instance
        this.set('map', map);
    }
});

Checklist

To make this question more "answerable", I think that a solution to this problem should fulfill the following requirements:

  1. When the property zoomLevel is changed, the map should change its zoom level accordingly (using map.setZoom(zoomLevel))
  2. When the user interactively changes the zoom on the map, the zoomLevel property should be changed (probably using leaflet map's zoomend event callback)

Notice that we have here a kind of "circular dependency", i.e "Do something (setZoom on map) when zoomLevel changes" and "when something happens (user changes zoom), change zoomLevel". I would like a solution that could avoid this circular observer dependencies. Ember's notifyPropertyChange could be a solution.

This seemed like an ideal task for Ember's computed properties, but I didn't know what to put in the dependent properties string. zoomLevel is basically a property that is dependent on something that isn't an ember property.


Update

My first attempt was to use an observer to set the zoom on leaflet and to bind an event on leaflet zoomend to set the zoomLevel on my view.

Problem: When I set zoomLevel on the zoomend leaflet event, I'm automatically triggering again my observer. Did I make myself clear?

So, this sequence of events happen:

  1. change zoomLevel on interactive map
  2. leaflet fires zoomend event
  3. zoomend event callback sets ember zoomLevel
  4. observer runs
  5. unecessarily calls setZoom on leaflet

This is not efficient, but I wouldn't mind if it worked. The problem is that when the user changes zoom multiple times very quickly (a simple long scroll on map) the observer is called multiple times, confusing leaflet.

like image 538
miguelcobain Avatar asked Mar 27 '13 11:03

miguelcobain


2 Answers

Ok, I think I've did it.

I used a computed property and an auxiliary property.

App.Leaflet = Ember.View.extend({
    classNames : ['ember-leaflet'],

    //default zoom level
    zoomLevelValue : 13,
    zoomLevel : function(key, value){
        // getter
        if (arguments.length === 1) {
            var zoomLevel = this.get('zoomLevelValue');
            return zoomLevel;
        // setter  
        } else{
            var map = this.get('map');

            this.set('zoomLevelValue', value);
            map.setZoom(value);

            return value;
        }
    }.property('zoomLevelValue'),

    didInsertElement : function() {
        var self = this;
        var zoomLevel = this.get('zoomLevel');

        var map = L.map(this.$().get(0)).setView(center, zoomLevel);

        // configure map instance...

        // Event listeners
        map.on('zoomend', function(e) {
            console.log('zoomend', 'Setting zoomLevel '+e.target.getZoom());
            self.set('zoomLevelValue', e.target.getZoom());
        });

        // save map instance
        this.set('map', map);

    }
});

Basically, there are two scenarios:

  1. zoomLevel was changed programatically -> we update the zoom on the map
  2. zoomLevel was changed on the map -> we set on the auxiliary property, and the computer property observers will also get notified, and we didn't set the zoom level on the map again

However I have a new problem, but I think it is leaflet related. When I call setZoom when another zoom animation on course, this setZoom is ignored. For example, executing:

map.setZoom(1);
map.setZoom(12);

ends up in the 1 zoom level.

Maybe a leaflet related SO question can help.

like image 186
miguelcobain Avatar answered Oct 21 '22 06:10

miguelcobain


To get from zoomLevel in ember to setZoom you can use an observer.

To go the other way you may have to bind an event to zoomChange on the leaflet

like image 24
davydotcom Avatar answered Oct 21 '22 05:10

davydotcom