CartoDB/cartodb20

View on GitHub
lib/assets/javascripts/cartodb/table/menu_modules/codemirror_color_picker.js

Summary

Maintainability
C
1 day
Test Coverage

  /**
   *  Show color picker when user clicks over
   *  a color in the Codemirror editor.
   *
   *  new cdb.admin.CodemirrorColorPicker({
   *    editor: codemirror-editor...
   *  })
   */

  cdb.admin.CodemirrorColorPicker = cdb.core.View.extend({

    initialize: function() {
      this.editor = this.options.editor;
      this._initBinds();
    },

    _initBinds: function() {
      _.bindAll(this, '_onClick', '_destroyPicker', '_onBlur', '_replaceColor');
      var self = this;

      this.editor.on('mousedown', function(ed, e){
        // Hack needed preventing cursor positioning bug
        setTimeout(function(){ self._onClick(e) }, 50);
      }, this);
      this.editor.on('blur',            this._onBlur,         this);
      this.editor.on('viewportChange',  this._destroyPicker,  this);
      this.editor.on('scroll',          this._destroyPicker,  this);
    },

    _disableBinds: function() {
      this.editor.off('mousedown');
      this.editor.off('blur',           this._onBlur,         this);
      this.editor.off('viewportChange', this._destroyPicker,  this);
      this.editor.off('scroll',         this._destroyPicker,  this);
      cdb.god.unbind('closeDialogs',    this._destroyPicker,  this);
    },

    _onClick: function(e) {
      e.preventDefault();

      var cursor = this.editor.getCursor(true);
      var token = this.editor.getTokenAt(cursor);

      if (token.type === "color" && e.target.nodeName.toLowerCase() === "span") {
        this._openColorPicker(e, cursor, token);
      } else if (this.color_picker) {
        this._destroyPicker();
      }
    },

    _openColorPicker: function(e, cursor, token) {
      this._destroyPicker();
      this._createPicker(e);
      $('body').append(this.color_picker.render().el);
      this.color_picker.init(token.string);
    },

    _createPicker: function(e) {
      this.color_picker = new cdb.admin.ColorPicker({
        target:       $(e.target),
        extra_colors: this._getCurrentUsedColors()
      }).bind("colorChosen", this._replaceColor, this);
      this._bindPickerEvents();
      this.addView(this.color_picker);
    },

    _getCurrentUsedColors: function() {
      if (!this.model) return [];
      
      // Tile style
      var style = this.model.get("tile_style");
      var cartoParser = new cdb.admin.CartoParser(style);
      return cartoParser.colorsUsed( { mode: "hex" });
    },

    // If user clicks over the color picker
    // we have to avoid blur event
    _bindPickerEvents: function() {
      if (this.color_picker) {
        var self = this;

        this.editor.on('blur', this._onBlur, this);

        this.color_picker.$el.on("mousedown", function() {
          self.editor.off('blur', self._onBlur, self);
        });

        this.color_picker.$el.on("mousemove", function() {
          self.editor.off('blur', self._onBlur, self);
        });

        this.color_picker.$el.on("mouseup", function() {
          self.editor.on('blur', self._onBlur, self);
        });

        setTimeout(function() {
          cdb.god.bind('closeDialogs', self._destroyPicker, self);
        },100)
      }
    },

    _unbindPickerEvents: function() {
      if (this.color_picker) {
        this.editor.on('blur', this._onBlur, this);
        this.color_picker.$el.off("mousedown mousemove mouseup");
        cdb.god.unbind('closeDialogs', this._destroyPicker, this);
      }
    },

    _destroyPicker: function() {
      if (this.color_picker) {
        this._unbindPickerEvents();
        this.removeView(this.color_picker);
        this.color_picker.hide();
        delete this.color_picker;
      }
    },

    _onBlur: function(ed) {
      this._destroyPicker();
    },

    _replaceColor: function(color, close) {
      var cursor = this.editor.getCursor();
      
      var nameMatch = this._getMatch(cursor, "name");
      var hexMatch = this._getMatch(cursor, "hex");
      var match = nameMatch ? nameMatch : hexMatch ;

      var start = {
        line: cursor.line,
        ch: match.start
      };
      var end = {
        line: cursor.line,
        ch: match.end
      };

      // Replace the color
      this.editor.replaceRange(color, start, end);

      // Focus the into the editor and set the cursor
      // It will let user to save style
      this.editor.focus();
      this.editor.setCursor(end);

      // Color chosen, let's trigger it!
      this.trigger('colorChosen', this);

      // Need to close picker?
      if (close) this._destroyPicker();
    },

    _getMatch: function(cursor, type) {
      if (!type) return;
      var re;

      switch (type.toLowerCase()) {
       case "name":
        re = new RegExp(color_keywords.join('|'),"g");
        break; 
       case "hsl":
        re = /hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)/g;
        break;
       case "rgb":
        re = /rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)/;
        break;
       case "hex":
        re = /#[a-fA-F0-9]{3,6}/g;
        break;
       default:
        cdb.log.info("invalid color match selection");
        return;
      }

      var line = this.editor.getLine(cursor.line);
      var match = re.exec(line);

      while (match) {
        var val = match[0];
        var len = val.length;
        var start = match.index;
        var end = match.index + len;
        if (cursor.ch >= start && cursor.ch <= end) {
          match = null;
          return {
            start: start,
            end: end,
            string: val
          };
        }
        match = re.exec(line);
      }
      return;
    },

    clean: function() {
      this._disableBinds();
      cdb.core.View.prototype.clean.call(this);
    }

  });