CartoDB/cartodb20

View on GitHub
lib/assets/javascripts/builder/components/modals/add-basemap/xyz/xyz-view.js

Summary

Maintainability
C
1 day
Test Coverage
var $ = require('jquery');
var _ = require('underscore');
var CoreView = require('backbone/core-view');
var template = require('./enter-url.tpl');
var CustomBaselayerModel = require('builder/data/custom-baselayer-model');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');

/**
 * Represents the XYZ tab content.
 */

module.exports = CoreView.extend({

  className: 'XYZPanel',

  events: {
    'click .js-tms': '_changeTMS',
    'keydown .js-url': '_onKeydown',
    'paste .js-url': '_onPaste'
  },

  initialize: function (opts) {
    if (!opts.submitButton) throw new Error('submitButton is required');

    this._submitButton = opts.submitButton;
    this._lastCallSeq = 0;
    this._debouncedUpdate = _.debounce(this._update.bind(this), 150);

    this._initBinds();
  },

  render: function () {
    this.clearSubViews();

    this._updateOkBtn();
    this._disableOkBtn(true);

    this.$el.html(
      template()
    );

    this._initViews();

    return this;
  },

  _initViews: function () {
    // Add TMS tooltip
    var tooltip = new TipsyTooltipView({
      el: this.$('.js-tms'),
      title: function () {
        return $(this).data('title');
      }
    });
    this.addView(tooltip);
  },

  _onKeydown: function (e) {
    e.stopPropagation();
    this._debouncedUpdate();
  },

  _onPaste: function (e) {
    e.stopPropagation();
    this._debouncedUpdate();
  },

  _initBinds: function () {
    this.model.bind('change:tms', this._setTMSCheckbox, this);
  },

  _update: function () {
    this._disableOkBtn(true);
    this._indicateIsValidating(true);
    var layer;
    var urlErrorMsg;

    var url = this.$('.js-url').val();
    var tms = this.model.get('tms');

    if (url) {
      try {
        layer = this._byCustomURL(url, tms);
      } catch (e) {
        urlErrorMsg = _t('components.modals.add-basemap.xyz.not-valid');
      }
    }

    this.model.set('layer', layer);

    if (layer) {
      var self = this;
      // Make sure only the last call made is the one that defines view change,
      // avoids laggy responses to indicate wrong state
      var thisCallSeq = ++this._lastCallSeq;
      layer.validateTemplateURL({
        success: function () {
          if (thisCallSeq === self._lastCallSeq) {
            self._disableOkBtn(false);
            self._indicateIsValidating(false);
            self._updateError();
          }
        },
        error: function () {
          if (thisCallSeq === self._lastCallSeq) {
            self._disableOkBtn(false);
            self._indicateIsValidating(false);
            self._updateError(_t('components.modals.add-basemap.xyz.couldnt-validate'));
          }
        }
      });
    } else if (url) {
      this._indicateIsValidating(false);
      this._updateError(urlErrorMsg);
    } else {
      this._indicateIsValidating(false);
      this._updateError();
    }
  },

  _changeTMS: function (e) {
    this.model.set('tms', !this.model.get('tms'));
    this._onKeydown(e);
  },

  _setTMSCheckbox: function (e) {
    this.$('.js-tms .Checkbox-input').toggleClass('is-checked', this.model.get('tms'));
  },

  _byCustomURL: function (url, tms) {
    // Minimal test for "valid URL" w/o having to complicate it with regex
    if (url && url.indexOf('/') === -1) throw new TypeError('invalid URL');

    // Only lowercase the placeholder variables, since the URL may contain case-sensitive data (e.g. API keys and such)
    url = url.replace(/\{S\}/g, '{s}')
      .replace(/\{X\}/g, '{x}')
      .replace(/\{Y\}/g, '{y}')
      .replace(/\{Z\}/g, '{z}');

    var layer = new CustomBaselayerModel({
      urlTemplate: url,
      attribution: null,
      maxZoom: 21,
      minZoom: 0,
      name: '',
      tms: tms,
      category: 'Custom',
      type: 'Tiled'
    });
    layer.set('className', layer._generateClassName(url));

    return layer;
  },

  _setTMS: function (ev) {
    var $checkbox = $(ev.target).closest('.Checkbox');
    $checkbox.find('.Checkbox-input').toggleClass('is-checked');
    this._update(ev);
  },

  _updateOkBtn: function () {
    this._submitButton.find('span').text(_t('components.modals.add-basemap.add-btn'));
  },

  _disableOkBtn: function (disable) {
    this._submitButton.toggleClass('is-disabled', disable);
  },

  _updateError: function (msg) {
    this.$('.js-error').text(msg)[ msg ? 'addClass' : 'removeClass' ]('is-visible');
  },

  _indicateIsValidating: function (indicate) {
    this.$('.js-validating').toggle(!!indicate);
  }

});