CartoDB/cartodb20

View on GitHub
lib/assets/javascripts/builder/editor/layers/layer-content-views/infowindow/infowindow-base-view.js

Summary

Maintainability
F
5 days
Test Coverage
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var _ = require('underscore');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var PanelWithOptionsView = require('builder/components/view-options/panel-with-options-view');
var InfoWindowHTMLView = require('./infowindow-html-view');
var InfowindowContentView = require('./infowindow-content-view');
var ScrollView = require('builder/components/scroll/scroll-view');
var TabPaneView = require('builder/components/tab-pane/tab-pane-view');
var TabPaneCollection = require('builder/components/tab-pane/tab-pane-collection');
var Toggler = require('builder/components/toggler/toggler-view');
var ApplyButtonView = require('builder/components/apply-button/apply-button-view');
var Infobox = require('builder/components/infobox/infobox-factory');
var InfoboxModel = require('builder/components/infobox/infobox-model');
var InfoboxCollection = require('builder/components/infobox/infobox-collection');
var MetricsTracker = require('builder/components/metrics/metrics-tracker');
var MetricsTypes = require('builder/components/metrics/metrics-types');

var cdb = require('internal-carto.js');

var REQUIRED_OPTS = [
  'editorModel',
  'layerDefinitionModel',
  'userActions'
];

module.exports = CoreView.extend({
  module: 'infowindow-base-view',

  initialize: function (opts) {
    checkAndBuildOpts(opts, REQUIRED_OPTS, this);

    this._initModels();
    this._initBinds();
    this._configPanes();
    this._initTemplates();
  },

  render: function () {
    this.clearSubViews();
    this.$el.empty();

    this._initViews();
    this._infoboxState();
    this._toggleOverlay();

    return this;
  },

  _initModels: function () {
    var content;
    if (this.model.isCustomTemplate()) {
      content = this.model.get('template');
    } else {
      content = this._getTemplateContent();
    }

    this._codemirrorModel = new Backbone.Model({
      content: content
    });

    this._infoboxModel = new InfoboxModel({
      state: this._isLayerHidden() ? 'layer-hidden' : ''
    });

    this._overlayModel = new Backbone.Model({
      visible: this._isLayerHidden()
    });

    // Set edition attribute in case custom html is applied and
    // in case none template is applied
    this._editorModel.set({
      edition: this.model.isCustomTemplate(),
      disabled: !this.model.hasTemplate() && !this.model.isCustomTemplate()
    });

    this._togglerModel = new Backbone.Model({
      labels: [_t('editor.infowindow.html-toggle.values'), _t('editor.infowindow.html-toggle.html')],
      active: this._editorModel.isEditing(),
      disabled: this._editorModel.isDisabled(),
      isDisableable: true,
      tooltip: _t('editor.infowindow.html-toggle.tooltip')
    });
  },

  _initViews: function () {
    var self = this;

    var infoboxSstates = [
      {
        state: 'confirm',
        createContentView: function () {
          return Infobox.createWithAction({
            type: 'alert',
            title: _t('editor.infowindow.messages.custom-html-applied.title'),
            body: _t('editor.infowindow.messages.custom-html-applied.body'),
            action: {
              label: _t('editor.infowindow.messages.custom-html-applied.accept')
            }
          });
        },
        onAction: self._cancelHTML.bind(self)
      }, {
        state: 'layer-hidden',
        createContentView: function () {
          return Infobox.createWithAction({
            type: 'alert',
            title: _t('editor.messages.layer-hidden.title'),
            body: _t('editor.messages.layer-hidden.body'),
            action: {
              label: _t('editor.messages.layer-hidden.show')
            }
          });
        },
        onAction: self._showHiddenLayer.bind(self)
      }
    ];

    var infoboxCollection = new InfoboxCollection(infoboxSstates);

    var panelWithOptionsView = new PanelWithOptionsView({
      className: 'Editor-content',
      editorModel: self._editorModel,
      infoboxModel: self._infoboxModel,
      infoboxCollection: infoboxCollection,
      createContentView: function () {
        return new TabPaneView({
          collection: self._collectionPane
        });
      },
      createControlView: function () {
        return new Toggler({
          model: self._togglerModel
        });
      },
      createActionView: function () {
        return new TabPaneView({
          collection: self._collectionPane,
          createContentKey: 'createActionView'
        });
      }
    });

    this.$el.append(panelWithOptionsView.render().el);
    this.addView(panelWithOptionsView);
  },

  _initBinds: function () {
    this.listenTo(this.model, 'change', this._onChange);
    this.listenTo(this._layerDefinitionModel, 'change:visible', this._infoboxState);

    this.listenTo(this._editorModel, 'change:edition', this._onChangeEdition);
    this.listenTo(this._editorModel, 'change:disabled', this._onChangeDisabled);
    this.listenTo(this._togglerModel, 'change:active', this._onTogglerChanged);
    this.listenTo(this._overlayModel, 'change:visible', this._toggleOverlay);
  },

  _toggleOverlay: function () {
    var isDisabled = this._overlayModel.get('visible');
    this.$('.js-overlay').toggleClass('is-disabled', isDisabled);
  },

  _setContent: function () {
    if (this._editorModel.get('edition')) {
      return;
    }

    var html_value = this._getTemplateContent();
    this._codemirrorModel.set({
      content: html_value
    });
  },

  _saveInfowindowHTML: function () {
    var content = this._codemirrorModel.get('content');
    // Parse
    var errors = this._getErrors(content);

    if (errors.length === 0) {
      this._clearErrors();

      // Save old fields
      if (!this.model.get('old_fields')) {
        this.model.saveFields();
      }

      // Save old template name
      if (!this.model.get('old_template_name')) {
        this.model.set('old_template_name', this.model.get('template_name'));
      }

      // new custom template
      this.model.set({
        template: content,
        template_name: ''
      });

      MetricsTracker.track(MetricsTypes.USED_ADVANCED_MODE, {
        mode_type: 'popup'
      });

      this._userActions.saveLayer(this._layerDefinitionModel);
    } else {
      this._parseErrors(errors);
    }
  },

  _getErrors: function (content) {
    try {
      var template = new cdb.core.Template({
        template: content,
        type: 'mustache'
      });
      template.compile()();
      if (content === '') {
        return [{
          line: 1,
          message: _t('editor.infowindow.code-mirror.errors.empty')
        }];
      } else {
        return [];
      }
    } catch (e) {
      return e;
    }
  },

  _showErrors: function (model) {
    var errors = this._querySchemaModel.get('query_errors');
    this._codemirrorModel.set('errors', this._parseErrors(errors));
  },

  _parseErrors: function (errors) {
    return errors.map(function (error) {
      return {
        line: error.line,
        message: error.message.split('\n\n')[1]
      };
    });
  },

  _clearErrors: function () {
    this._codemirrorModel.set({ errors: [] });
  },

  _getTemplateContent: function () {
    var fields = [];
    var alternative_names = this.model.get('alternative_names');

    _.each(this.model.get('fields'), function (field, i) {
      var f = _.clone(field);
      f.position = i;
      if (alternative_names && alternative_names[f.name]) {
        f.alternative_name = alternative_names[f.name];
      }
      fields.push(f);
    });

    var tmpl = new cdb.core.Template({
      template: this.model.getTemplate(),
      type: 'mustache'
    });

    return tmpl.render({ content: { fields: fields } });
  },

  _configPanes: function () {
    var self = this;

    var tabPaneTabs = [{
      selected: !self.model.isCustomTemplate(),
      createContentView: function () {
        return new ScrollView({
          createContentView: function () {
            return new InfowindowContentView(_.extend(self.options, {
              templates: self._templates
            }));
          }
        });
      },
      createActionView: function () {
        return new CoreView();
      }
    }, {
      selected: self.model.isCustomTemplate(),
      createContentView: function () {
        return new InfoWindowHTMLView({
          onApplyEvent: self._saveInfowindowHTML.bind(self),
          codemirrorModel: self._codemirrorModel
        });
      },
      createActionView: function () {
        return new ApplyButtonView({
          onApplyClick: self._saveInfowindowHTML.bind(self),
          overlayModel: self._overlayModel
        });
      }
    }];

    this._collectionPane = new TabPaneCollection(tabPaneTabs);
  },

  _onChange: function () {
    this._userActions.saveLayer(this._layerDefinitionModel);
    this._setContent();
  },

  _onChangeEdition: function () {
    this._infoboxState();

    var edition = this._editorModel.get('edition');
    var index = edition ? 1 : 0;
    this._collectionPane.at(index).set({ selected: true });
    this._togglerModel.set({ active: edition });
  },

  _onChangeDisabled: function () {
    var disabled = this._editorModel.get('disabled');
    this._togglerModel.set({ disabled: disabled });
  },

  _onTogglerChanged: function () {
    var checked = this._togglerModel.get('active');
    this._editorModel.set({ edition: checked });
  },

  _cancelHTML: function () {
    this.model.setDefault();
    this._infoboxModel.set({ state: '' });
    this._overlayModel.set({ visible: false });
  },

  _isLayerHidden: function () {
    return this._layerDefinitionModel.get('visible') === false;
  },

  _infoboxState: function () {
    var edition = this._editorModel.get('edition');
    var customTemplate = this.model.isCustomTemplate();

    if (!edition && customTemplate) {
      this._infoboxModel.set({ state: 'confirm' });
      this._overlayModel.set({ visible: true });
    } else if (this._isLayerHidden()) {
      this._infoboxModel.set({ state: 'layer-hidden' });
      this._overlayModel.set({ visible: true });
      this._togglerModel.set({ disabled: true });
    } else {
      this._infoboxModel.set({ state: '' });
      this._overlayModel.set({ visible: false });
      this._togglerModel.set({ disabled: false });
    }
  },

  _showHiddenLayer: function () {
    var savingOptions = {
      shouldPreserveAutoStyle: true
    };
    this._layerDefinitionModel.toggleVisible();
    this._userActions.saveLayer(this._layerDefinitionModel, savingOptions);
  }
});