Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to toggle KML layers using Knockout.js without re-creating the map?

I have created a Knockout binding to be able to toggle KML layers with Google Maps, but the solution seems a bit slow and "flickerish". How can I avoid re-creating the map and layers on each toggle?

A running demo can be found here

var ViewModel = function () {
    var self = this;

    self.mapOptions = {
        center: new google.maps.LatLng(60.390791, 5.306396),
        zoom: 2
    };

    self.levels = [{
        text: "Type 1",
        countries: ko.observableArray([
            'https://dl.dropbox.com/u/2873968/countries-kml/afghanistan.kml',
            'https://dl.dropbox.com/u/2873968/countries-kml/algeria.kml',
            'https://dl.dropbox.com/u/2873968/countries-kml/bahrain.kml',
            'https://dl.dropbox.com/u/2873968/countries-kml/burundi.kml',
            'https://dl.dropbox.com/u/2873968/countries-kml/ca_republic.kml',
            'https://dl.dropbox.com/u/2873968/countries-kml/cameroon.kml',
            'https://dl.dropbox.com/u/2873968/countries-kml/chad.kml',
            'https://dl.dropbox.com/u/2873968/countries-kml/colombia.kml',
            'https://dl.dropbox.com/u/2873968/countries-kml/dr_congo.kml']),
        isVisible: ko.observable(false)
    }, {
        text: "Type 2",
        countries: ko.observableArray([
            'https://dl.dropbox.com/u/2873968/countries-kml/russia.kml',
            'https://dl.dropbox.com/u/2873968/countries-kml/sudan.kml',
            'https://dl.dropbox.com/u/2873968/countries-kml/syria.kml',
            'https://dl.dropbox.com/u/2873968/countries-kml/thailand.kml',
            'https://dl.dropbox.com/u/2873968/countries-kml/venezuela.kml',
            'https://dl.dropbox.com/u/2873968/countries-kml/yemen.kml',
            'https://dl.dropbox.com/u/2873968/countries-kml/zimbabwe.kml']),
        isVisible: ko.observable(true)
    }];
};

ko.bindingHandlers.KML = {
    update: function (element, valueAccessor) {
        var data = ko.utils.unwrapObservable(valueAccessor()),
            mapOptions = ko.utils.unwrapObservable(data.mapOptions) || {},
            levels = ko.utils.unwrapObservable(data.levels) || [],
            map = new google.maps.Map(element, mapOptions);

        for (var i = 0; i < levels.length; i++) {
            var level = levels[i],
                isVisible = level.isVisible(),
                text = level.text,
                countries = ko.utils.unwrapObservable(level.countries) || [];

            for (var y = 0; y < countries.length; y++) {
                var country = countries[y],
                    layer = new google.maps.KmlLayer(country, {
                        map: map,
                        preserveViewport: true
                    });

                if (isVisible) {
                    layer.setMap(map);
                } else {
                    layer.setMap(null);
                }

            }
        }
    }
};

ko.applyBindings(new ViewModel());
like image 863
Remi Sture Avatar asked May 15 '14 08:05

Remi Sture


1 Answers

First thing to do is at least use the init callback.

ko.bindingHandlers.KML = {
    init: function (element, valueAccessor) {
        var data = ko.utils.unwrapObservable(valueAccessor()),
            mapOptions = ko.utils.unwrapObservable(data.mapOptions) || {},
            levels = ko.utils.unwrapObservable(data.levels) || [],
            map = new google.maps.Map(element, mapOptions);

        // now that the map is created, create layers for each level in each country

        // set the layers visibility

    }
}

Then, in the update callback you only need to update the visibility of the layers

ko.bindingHandlers.KML = {
    init: function (element, valueAccessor) {

    },
    update: function(element, valueAccessor){
        // get data from valueAccessor

        // for each level, set visibility
    }
}

But, now we don't have a map to use in the update callback. Fortunately we can create our own objects inside our bindingHanlder, so let's do that:

ko.bindingHandlers.KML = {
    config : {
       map: {}  
    }, 
    init: function (element, valueAccessor) {
        var map = new google.maps.Map(element, mapOptions);

        // now we can store our map;
        ko.bindingHandlers.KML.config.map = map;

    },
    update: function(element, valueAccessor){
        // and use it in the update
        var map ko.bindingHandlers.KML.config.map;
    }
}

This also means we can define a model for our layer as well and let that model control visibility through an observable.

This all results in the following jsFiddle example

like image 75
Major Byte Avatar answered Nov 19 '22 12:11

Major Byte