CartoDB/cartodb20

View on GitHub
lib/assets/javascripts/builder/routes/router.js

Summary

Maintainability
C
7 hrs
Test Coverage
var Backbone = require('backbone');
var _ = require('underscore');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var populateRoute = require('./populate-route');

var ROUTE_LAYERS = 'layers';
var ROUTE_WIDGETS = 'widgets';
var ROUTE_SETTINGS = 'settings';
var BASE_LAYER_ROUTE = 'layer/:layerId';
var ROUTE_WIDGET = 'widget/:widgetId';
var ROUTE_BASEMAP = 'basemap';
var ROUTE_LAYER_DATA_TAB = BASE_LAYER_ROUTE + '/data';
var ROUTE_LAYER_ANALYSES_TAB = BASE_LAYER_ROUTE + '/analyses(/:nodeId)';
var ROUTE_LAYER_STYLE_TAB = BASE_LAYER_ROUTE + '/style';
var ROUTE_LAYER_POPUPS_TAB = BASE_LAYER_ROUTE + '/popups';
var ROUTE_LAYER_LEGENDS_TAB = BASE_LAYER_ROUTE + '/legends';
var ROUTE_MODAL = 'modal';
var ROUTE_ADD_POINT = BASE_LAYER_ROUTE + '/add/point';
var ROUTE_ADD_LINE = BASE_LAYER_ROUTE + '/add/line';
var ROUTE_ADD_POLYGON = BASE_LAYER_ROUTE + '/add/polygon';
var EDIT_FEATURE = BASE_LAYER_ROUTE + '/edit/:featureId';

var REQUIRED_OPTS = [
  'modals',
  'editorModel',
  'widgetDefinitionsCollection',
  'handleModalsRoute',
  'handleAnalysesRoute',
  'handleWidgetRoute'
];

var ROUTES = [
  ['root', ''],
  ['root', ROUTE_LAYERS],
  ['settings', ROUTE_SETTINGS],
  ['layer', BASE_LAYER_ROUTE],
  ['widgets', ROUTE_WIDGETS],
  ['widget', ROUTE_WIDGET],
  ['layer_data', ROUTE_LAYER_DATA_TAB],
  ['layer_analyses', ROUTE_LAYER_ANALYSES_TAB],
  ['layer_style', ROUTE_LAYER_STYLE_TAB],
  ['layer_style', BASE_LAYER_ROUTE],
  ['layer_popups', ROUTE_LAYER_POPUPS_TAB],
  ['layer_legends', ROUTE_LAYER_LEGENDS_TAB],
  ['add_feature_point', ROUTE_ADD_POINT],
  ['add_feature_line', ROUTE_ADD_LINE],
  ['add_feature_polygon', ROUTE_ADD_POLYGON],
  ['edit_feature', EDIT_FEATURE],
  ['modal', ROUTE_MODAL],
  ['basemap', ROUTE_BASEMAP]
];

module.exports = (function () {
  var initialized = false;
  var routeModel = new Backbone.Model();
  // keep last route before a modal, so we can replace /modal with it
  var previousRoute;
  // Routes that should not be kept as a "previous" route
  var UNSTORABLE_ROUTES;

  function generateRoute (routeName, model) {
    return function () {
      model.set('currentRoute', [routeName].concat(Array.prototype.slice.call(arguments)));
    };
  }

  return {
    init: function (options) {
      if (initialized) {
        throw new Error('Router can only be initialized once');
      }

      checkAndBuildOpts(options, REQUIRED_OPTS, this);

      var self = this;
      initialized = true;
      this.appRouter = new Backbone.Router();

      ROUTES.forEach(function (route) {
        self.appRouter.route(route[1], generateRoute(route[0], routeModel));
      });
      UNSTORABLE_ROUTES = [
        ROUTE_ADD_POINT,
        ROUTE_ADD_LINE,
        ROUTE_ADD_POLYGON,
        EDIT_FEATURE
      ].map(function (route) {
        return self.appRouter._routeToRegExp(route);
      });

      this._initBinds();
    },

    _initBinds: function () {
      this.getRouteModel().on('change:currentRoute', this._onChangeRoute, this);
    },

    _onChangeRoute: function (routeModel) {
      var currentRoute = routeModel.get('currentRoute');

      if (!currentRoute) return;

      this._handleModalsRoute(currentRoute, this._modals);
      this._handleAnalysesRoute(currentRoute);
      this._handleWidgetRoute(currentRoute, this._widgetDefinitionsCollection);

      this._editorModel.set('edition', false);
    },

    getRouter: function () {
      return this.appRouter;
    },

    getRouteModel: function () {
      return routeModel;
    },

    goBack: function () {
      Backbone.history.history.back();
    },

    goToDefaultRoute: function () {
      this.navigate('');
    },

    replaceWithRoot: function () {
      this.navigate('', { replace: true });
    },

    goToBaseMap: function (layerId) {
      this.navigate(ROUTE_BASEMAP);
    },

    goToLayerList: function () {
      this.navigate(ROUTE_LAYERS);
    },

    goToSettings: function () {
      this.navigate(ROUTE_SETTINGS);
    },

    goToDataTab: function (layerId) {
      this.navigate(populateRoute(ROUTE_LAYER_DATA_TAB, { layerId: layerId }));
    },

    goToAnalysisTab: function (layerId, options) {
      this.navigate(populateRoute(ROUTE_LAYER_ANALYSES_TAB, { layerId: layerId }), options);
    },

    goToAnalysisNode: function (layerId, nodeId, options) {
      var args = { layerId: layerId, nodeId: nodeId };

      this.navigate(populateRoute(ROUTE_LAYER_ANALYSES_TAB, args), options);
    },

    goToStyleTab: function (layerId) {
      this.navigate(populateRoute(ROUTE_LAYER_STYLE_TAB, { layerId: layerId }));
    },

    goToPopupsTab: function (layerId) {
      this.navigate(populateRoute(ROUTE_LAYER_POPUPS_TAB, { layerId: layerId }));
    },

    goToLegendsTab: function (layerId) {
      this.navigate(populateRoute(ROUTE_LAYER_LEGENDS_TAB, { layerId: layerId }));
    },

    goToWidgetList: function () {
      this.navigate(ROUTE_WIDGETS);
    },

    goToWidget: function (widgetId) {
      var route = routeModel.get('currentRoute');
      var options = {};

      if (_.isArray(route) && route[0] === 'widget') {
        options.replace = true;
      }

      this.navigate(populateRoute(ROUTE_WIDGET, { widgetId: widgetId }), options);
    },

    getCurrentRoute: function () {
      return Backbone.history.getPath();
    },

    saveCurrentRoute: function () {
      var currentRoute = this.getCurrentRoute();
      var canBeSaved = !UNSTORABLE_ROUTES.some(function (regex) {
        return regex.test(currentRoute);
      });

      if (canBeSaved === true) {
        previousRoute = currentRoute;
      }
    },

    addPoint: function (layerId) {
      this._addFeature(ROUTE_ADD_POINT, layerId);
    },

    addLine: function (layerId) {
      this._addFeature(ROUTE_ADD_LINE, layerId);
    },

    addPolygon: function (layerId) {
      this._addFeature(ROUTE_ADD_POLYGON, layerId);
    },

    goToPreviousRoute: function (options) {
      previousRoute = previousRoute || options.fallback;

      if (previousRoute !== null && previousRoute !== undefined) {
        this.navigate(previousRoute, options.options);
        previousRoute = null;
      } else {
        Backbone.history.history.back();
      }
    },

    _addFeature: function (route, layerId) {
      this.navigate(populateRoute(route, { layerId: layerId }));
    },

    editFeature: function (feature) {
      var featureId = feature.get('cartodb_id');
      var layerId = feature.getLayerId();

      this.navigate(populateRoute(EDIT_FEATURE, {
        layerId: layerId,
        featureId: featureId
      }));
    },

    navigate: function (route, options) {
      this.saveCurrentRoute();

      options = _.extend({
        trigger: true
      }, options);

      this.appRouter.navigate(route, options);
    },

    replaceState: function (route) {
      var root = Backbone.history.root;
      var newRoute = root + route;

      window.history.replaceState(null, null, newRoute);
    }
  };
})();