Vizzuality/gfw-climate

View on GitHub
app/assets/javascripts/map/services/PlaceService.js

Summary

Maintainability
B
6 hrs
Test Coverage
/**
 * The PlaceService class manages places in the application.
 *
 * A place is just the current state of the application which can be
 * represented as an Object or a URL. For example, the place associated with:
 *
 *   http://localhost:5000/map/6/2/17/ALL/terrain/loss
 *
 * Can also be represented like this:
 *
 *  zoom - 6
 *  lat - 2
 *  lng - 17
 *  iso - ALL
 *  maptype - terrain
 *  baselayers - loss
 *
 * The PlaceService class handles the following use cases:
 *
 * 1) New route updates views
 *
 *   The Router receives a new URL and all application views need to be updated
 *   with the state encoded in the URL.
 *
 *   Here the router publishes the "Place/update" event passing in the route
 *   name and route parameters. The PlaceService handles the event by
 *   standardizing the route parameters and publishing them in a "Place/go"
 *   event. Any presenters listening to the event receives the updated
 *   application state and can update their views.
 *
 * 2) Updated view updates URL
 *
 *   A View state changes (e.g., a new map zoom) and the URL needs to be
 *   updated, not only with its new state, but from the state of all views in
 *   the application that provide state for URLs.
 *
 *   Here presenters publishe the "Place/register" event passing in a
 *   reference to themselves. The PlaceService subscribes to the
 *   "Place/register" event so that it can keep references to all presenters
 *   that provide state. Then the view publishes the "Place/update" event
 *   passing in a "go" parameter. If "go" is false, the PlaceService will
 *   update the URL. Otherwise it will publish the "Place/go" event which will
 *   notify all subscribed presenters.
 *
 * @return {PlaceService} The PlaceService class
 */
define(
  [
    'underscore',
    'mps',
    'uri',
    'map/presenters/PresenterClass',
    'map/services/LayerSpecService',
    'map/services/GeostoreService'
  ],
  function(
    _,
    mps,
    UriTemplate,
    PresenterClass,
    layerSpecService,
    GeostoreService
  ) {
    'use strict';

    var urlDefaultsParams = {
      baselayers: 'biomass_loss',
      zoom: 3,
      lat: 15,
      lng: 27,
      maptype: 'dark',
      iso: 'ALL'
    };

    var PlaceService = PresenterClass.extend({
      _uriTemplate:
        '{name}{/zoom}{/lat}{/lng}{/iso}{/maptype}{/baselayers}{/sublayers}{?geojson,geostore,wdpaid,begin,end,threshold,dont_analyze,rangearray}',

      /**
       * Create new PlaceService with supplied Backbone.Router.
       *
       * @param  {Backbond.Router} router Instance of Backbone.Router
       */
      init: function(router) {
        this.router = router;
        this._presenters = [];
        this._name = null;
        this._presenters.push(layerSpecService); // this makes the test fail
        this._super();
      },

      /**
       * Subscribe to application events.
       */
      _subscriptions: [
        {
          'Place/register': function(presenter) {
            this._presenters = _.union(this._presenters, [presenter]);
          }
        },
        {
          'Place/update': function() {
            this._updatePlace();
          }
        }
      ],

      /**
       * Init by the router to set the name
       * and publish the first place.
       *
       * @param  {String} name   Place name
       * @param  {Object} params Url params
       */
      initPlace: function(name, params) {
        this._name = name;
        this._newPlace(params);
      },

      /**
       * Silently updates the url from the presenter params.
       */
      _updatePlace: function() {
        var route, params;
        params = this._destandardizeParams(
          this._getPresenterParams(this._presenters)
        );

        route = this._getRoute(params);
        this.router.navigateTo(route, { silent: true });
      },

      /**
       * Handles a new place.
       *
       * @param  {Object}  params The place parameters
       */
      _newPlace: function(params) {
        var where,
          place = {};

        place.params = this._standardizeParams(params);

        where = _.union(place.params.baselayers, place.params.sublayers);

        layerSpecService.toggle(
          where,
          _.bind(function(layerSpec) {
            place.layerSpec = layerSpec;
            mps.publish('Place/go', [place]);
          }, this)
        );

        if (place.params.geostore) {
          GeostoreService.get(place.params.geostore).then(function(geostore) {
            mps.publish('Geostore/go', [geostore]);
          });
        }
      },

      /**
       * Return route URL for supplied route name and route params.
       *
       * @param  {Object} params The route params
       * @return {string} The route URL
       */
      _getRoute: function(param) {
        var url = new UriTemplate(this._uriTemplate).fillFromObject(param);
        return decodeURIComponent(url);
      },

      /**
       * Return standardized representation of supplied params object.
       *
       * @param  {Object} params The params to standardize
       * @return {Object} The standardized params.
       */
      _standardizeParams: function(params) {
        var p = _.extendNonNull({}, urlDefaultsParams, params);
        p.name = this._name;

        p.baselayers = _.map(p.baselayers.split(','), function(slug) {
          return { slug: slug };
        });

        p.sublayers = p.sublayers
          ? _.map(p.sublayers.split(','), function(id) {
              return { id: _.toNumber(id) };
            })
          : [];

        p.zoom = _.toNumber(p.zoom);
        p.lat = _.toNumber(p.lat);
        p.lng = _.toNumber(p.lng);
        p.iso = _.object(['country', 'region'], p.iso.split('-'));
        p.begin = p.begin ? p.begin.format('YYYY-MM-DD') : null;
        p.end = p.end ? p.end.format('YYYY-MM-DD') : null;
        p.geojson = p.geojson
          ? JSON.parse(decodeURIComponent(p.geojson))
          : null;
        p.wdpaid = p.wdpaid ? _.toNumber(p.wdpaid) : null;
        p.threshold = p.threshold ? _.toNumber(p.threshold) : null;
        p.subscribe_alerts = p.subscribe_alerts === 'subscribe' ? true : null;
        p.rangearray = p.rangearray
          ? JSON.parse(atob(encodeURI(p.rangearray)))
          : null;
        p.referral = p.referral;
        return p;
      },

      /**
       * Return formated URL representation of supplied params object based on
       * a route name.
       *
       * @param  {Object} params Place to standardize
       * @return {Object} Params ready for URL
       */
      _destandardizeParams: function(params) {
        var p = _.extendNonNull({}, urlDefaultsParams, params);
        var baselayers = _.pluck(p.baselayers, 'slug');
        p.name = this._name;
        p.baselayers = baselayers.length > 0 ? baselayers : 'none';
        p.sublayers = p.sublayers ? p.sublayers.join(',') : null;
        p.zoom = String(p.zoom);
        p.lat = p.lat.toFixed(2);
        p.lng = p.lng.toFixed(2);
        p.iso = _.compact(_.values(p.iso)).join('-') || 'ALL';
        p.begin = p.begin ? p.begin.format('YYYY-MM-DD') : null;
        p.end = p.end ? p.end.format('YYYY-MM-DD') : null;
        p.geojson = p.geojson ? encodeURIComponent(p.geojson) : null;
        p.wdpaid = p.wdpaid ? String(p.wdpaid) : null;
        p.threshold = p.threshold ? String(p.threshold) : null;
        p.rangearray = p.rangearray
          ? encodeURI(btoa(JSON.stringify(p.rangearray)))
          : null;
        return p;
      },

      /**
       * Return param object representing state from all registered presenters
       * that implement getPlaceParams().
       *
       * @param  {Array} presenters The registered presenters
       * @return {Object} Params representing state from all presenters
       */
      _getPresenterParams: function(presenters) {
        var p = {};

        _.each(
          presenters,
          function(presenter) {
            _.extend(p, presenter.getPlaceParams());
          },
          this
        );
        return p;
      }
    });

    return PlaceService;
  }
);