0

I have a GoogleMap with EmberJs view. Everything works good except the data binding.

I want to bind the map markers with ember-data. If something changes at data level it must reflect on Map.

I tried to use observer, and re-run the makeMarkers method to set the marker, but that seems to be a bad solution.

What would be the best way to bind data with GoogleMaps?

Shahroon
  • 307
  • 2
  • 15

2 Answers2

2

View is deprecated on Ember 2.0, they will be removed at Ember 2.5, make a component like: {{g-map markers=models}}

This component have a collection of items, here markers.
You can implement something like this:

import Ember from 'ember';
import MarkerSync from '../mixin/marker-synchronizer';

/**
 * Basic Component to display a google map,
 * Service & Marker have to be improved
 **/
export default Ember.Component.extend(MarkerSync, {
    classNames: 'google-map',
    googleMap: Ember.inject.service(),
    map: null,

    mapOptions: function () {
        return {
            center: new google.maps.LatLng(-34.397, 150.644),
            zoom: 8
        };
    },

    didInsertElement: function () {
        this.$().height('100%');
        this.$().width('100%');
        this.displayGmap();
        jQuery(window).on('resize', Ember.run.bind(this, this.handleResize));
    },

    willInsertElement: function () {
        this.get('googleMap').loadScript();
    },

    displayGmap: Ember.observer('googleMap.isLoaded', function () {
        if (!this.get('googleMap.isLoaded')) {
            return;
        }
        const mapOptions = this.mapOptions();
        this.set('map', new google.maps.Map(this.$()[0], mapOptions));
    }),

    handleResize: function () {
        if (!this.get('googleMap.isLoaded')){
            return;
        }
        const map = this.get('map');
        const center = map.getCenter();
        google.maps.event.trigger(map, 'resize');
        map.setCenter(center);
    },
});

import Ember from 'ember';

/**
 * Synchronize collection with map from component.
 * Care about to display or remove marker from map,
 * Be careful this is not optimized.
 **/
export
default Ember.Mixin.create({
    markers: null,
    _gHash: Ember.A(),
    init() {
        this._super.apply(this, arguments);
        /*
        * observes markers array.
        */
        this.get('markers').addArrayObserver({
            arrayWillChange: Ember.run.bind(this, this.markersWillChange),
            arrayDidChange: Ember.run.bind(this, this.markersDidChange)
        });
    },

    /*
    * Remove marker from array and remove from map
    */
    markerRemoved(marker) {
        let gMarker = this.get('_gHash').find(function(item) {
            return item.related === marker;
        });
        gMarker.native.setMap(null);
        this.get('_gHash').removeObject(gMarker);
    },

    /*
    * Add marker to `synchronized` array and display on map
    */
    markerAdded(marker) {
        const gMarker = new google.maps.Marker({
            position: {
                lat: marker.lat,
                lng: marker.lng
            },
            title: marker.title,
            map: this.get('map'),
        });
        this.get('_gHash').push({
            native: gMarker,
            related: marker
        });
    },

    /*
    * Take care about removed item
    */
    markersWillChange(markers, start, removeCount, addCount) {
        if (removeCount > 0) {
            for (let i = start; i < start + removeCount; i++) {
                this.markerRemoved(markers.objectAt(i));
            }
        }
    },

    /*
    * Take care about added item
    */
    markersDidChange(markers, start, removeCount, addCount) {
        if (addCount > 0) {
            for (let i = start; i < start + addCount; i++) {
                this.markerAdded(markers.objectAt(i));
            }
        }
    },
});

import Ember from 'ember';

const get = Ember.get;

/**
* This service lazy load googleMap api.
* Ugly but do the job
*/
export default Ember.Service.extend({

    scriptUrl: 'https://maps.googleapis.com/maps/api/js',
    isLoaded: Ember.computed.equal('state', 'loaded'),
    state: 'none',

    init: function () {
        let config = this.container.lookupFactory('config:environment');
        var apiKey = get(config, 'googleMap.apiKey');
        this.set('apiKey', apiKey);
    },

    normalizeUrl: function () {
        var url = this.get('scriptUrl');
        url += '?' + 'v=3' + '&' + 'libraries=places' + '&' + 'callback=loadGmap';
        if (this.get('apiKey')) {
            url += '&key=' + this.get('apiKey');
        }
        return url;
    },

    loadScript: function () {
        if (this.get('state') !== 'none'){
            return false;
        }
        this.set('state', 'loading');
        window.loadGmap = Ember.run.bind(this, function () {
            this.set('state', 'loaded');
        });
        var url = this.normalizeUrl();
        return Ember.$.getScript(url).fail(function(){
            console.log('getScript fail');
        });
    },
});

This implementation work but you have to 'sanitize' this code :)

Thibault Remy
  • 259
  • 2
  • 7
1

1) Create a component, not a view

2) Use didInsertElement to render google map and observer to update it. Don't forget that observers are synchronous (http://guides.emberjs.com/v1.13.0/object-model/observers/) and you need to do smth like:

somethingChanged: Ember.observer('something', function () {
    Ember.run.once(this, '_somethingChanged');
}).on('init'),
_somethingChanged: function () {
    /* do smth about changed property here */
}
Gennady Dogaev
  • 5,902
  • 1
  • 15
  • 23
  • If I use component then how can I access Model data in there? – Shahroon Aug 19 '15 at 14:39
  • 1
    You can pass any data to your component: `{{my-component data=model someParam=someValue}}`, you can even pass a `store` in this way (not best decision, but in some rare cases you may need it) – Gennady Dogaev Aug 19 '15 at 19:06
  • thanks, your suggestions till now seems to be working for me. Now two things, 1> How can I get the only changed object in _somethingChanged? 2> If I don't get the changed object and try to rerun makeMarkers for all the data, it starts making the markers multiple times.. – Shahroon Aug 20 '15 at 07:12
  • Do you mean that you passing an array to component and need to know what element was changed? I did not understand – Gennady Dogaev Aug 20 '15 at 10:53
  • Yes, I passed an array, and if some attribute values changes inside that array, I want to get that object. – Shahroon Aug 20 '15 at 11:14
  • You may rerender a component: remove `.on('init)` in code above and put in _somethingChanged a line `this.rerender()`. Docs: http://emberjs.com/api/classes/Ember.Component.html#method_rerender Or you may save a copy of attribute and compare old and new array. – Gennady Dogaev Aug 20 '15 at 11:53