<commit_before>(function() {

var $ = jQuery; // Handle namespaced jQuery

// This is the visual search facet that holds the category and its autocompleted
// input field.
VS.ui.SearchFacet = Backbone.View.extend({

  type : 'facet',

  className : 'search_facet',

  events : {
    'click .category'           : 'selectFacet',
    'keydown input'             : 'keydown',
    'mousedown input'           : 'enableEdit',
    'mouseover .VS-icon-cancel' : 'showDelete',
    'mouseout .VS-icon-cancel'  : 'hideDelete',
    'click .VS-icon-cancel'     : 'remove'
  },

  initialize : function(options) {
    this.flags = {
      canClose : false
    };
    _.bindAll(this, 'set', 'keydown', 'deselectFacet', 'deferDisableEdit');
  },

  // Rendering the facet sets up autocompletion, events on blur, and populates
  // the facet's input with its starting value.
  render : function() {
    $(this.el).html(JST['search_facet']({
      model : this.model
    }));

    this.setMode('not', 'editing');
    this.setMode('not', 'selected');
    this.box = this.$('input');
    this.box.val(this.model.label());
    this.box.bind('blur', this.deferDisableEdit);
    // Handle paste events with `propertychange`
    this.box.bind('input propertychange', this.keydown);
    this.setupAutocomplete();

    return this;
  },

  // This method is used to setup the facet's input to auto-grow.
  // This is defered in the searchBox so it can be attached to the
  // DOM to get the correct font-size.
  calculateSize : function() {
    this.box.autoGrowInput();
    this.box.unbind('updated.autogrow');
    this.box.bind('updated.autogrow', _.bind(this.moveAutocomplete, this));
  },

  // Forces a recalculation of this facet's input field's value. Called when
  // the facet is focused, removed, or otherwise modified.
  resize : function(e) {
    this.box.trigger('resize.autogrow', e);
  },

  // Watches the facet's input field to see if it matches the beginnings of
  // words in `autocompleteValues`, which is different for every category.
  // If the value, when selected from the autocompletion menu, is different
  // than what it was, commit the facet and search for it.
  setupAutocomplete : function() {
    this.box.autocomplete({
      source    : _.bind(this.autocompleteValues, this),
      minLength : 0,
      delay     : 0,
      autoFocus : true,
      position  : {offset : "0 5"},
      create    : _.bind(function(e, ui) {
        $(this.el).find('.ui-autocomplete-input').css('z-index','auto');
      }, this),
      select    : _.bind(function(e, ui) {
        e.preventDefault();
        var originalValue = this.model.get('value');
        this.set(ui.item.value);
        if (originalValue != ui.item.value || this.box.val() != ui.item.value) {
          if (this.options.app.options.autosearch) {
            this.search(e);
          } else {
              this.options.app.searchBox.renderFacets();
              this.options.app.searchBox.focusNextFacet(this, 1, {viewPosition: this.options.order});
          }
        }
        return false;
      }, this),
      open      : _.bind(function(e, ui) {
        var box = this.box;
        this.box.autocomplete('widget').find('.ui-menu-item').each(function() {
          var $value = $(this),
              autoCompleteData = $value.data('item.autocomplete') || $value.data('ui-autocomplete-item');

          if (autoCompleteData['value'] == box.val() && box.data('uiAutocomplete').menu.activate) {
            box.data('uiAutocomplete').menu.activate(new $.Event("mouseover"), $value);
          }
        });
      }, this)
    });

    this.box.autocomplete('widget').addClass('VS-interface');
  },

  // As the facet's input field grows, it may move to the next line in the
  // search box. `autoGrowInput` triggers an `updated` event on the input
  // field, which is bound to this method to move the autocomplete menu.
  moveAutocomplete : function() {
    var autocomplete = this.box.data('uiAutocomplete');
    if (autocomplete) {
      autocomplete.menu.element.position({
        my        : "left top",
        at        : "left bottom",
        of        : this.box.data('uiAutocomplete').element,
        collision : "flip",
        offset    : "0 5"
      });
    }
  },

  // When a user enters a facet and it is being edited, immediately show
  // the autocomplete menu and size it to match the contents.
  searchAutocomplete : function(e) {
    var autocomplete = this.box.data('uiAutocomplete');
    if (autocomplete) {
      var menu = autocomplete.menu.element;
      autocomplete.search();

      // Resize the menu based on the correctly measured width of what's bigger:
      // the menu's original size or the menu items' new size.
      menu.outerWidth(Math.max(
        menu.width('').outerWidth(),
        autocomplete.element.outerWidth()
      ));
    }
  },

  // Closes the autocomplete menu. Called on disabling, selecting, deselecting,
  // and anything else that takes focus out of the facet's input field.
  closeAutocomplete : function() {
    var autocomplete = this.box.data('uiAutocomplete');
    if (autocomplete) autocomplete.close();
  },

  // Search terms used in the autocomplete menu. These are specific to the facet,
  // and only match for the facet's category. The values are then matched on the
  // first letter of any word in matches, and finally sorted according to the
  // value's own category. You can pass `preserveOrder` as an option in the
  // `facetMatches` callback to skip any further ordering done client-side.
  autocompleteValues : function(req, resp) {
    var category = this.model.get('category');
    var value    = this.model.get('value');
    var searchTerm = req.term;

    this.options.app.options.callbacks.valueMatches(category, searchTerm, function(matches, options) {
      options = options || {};
      matches = matches || [];

      if (searchTerm && value != searchTerm) {
        if (options.preserveMatches) {
          resp(matches);
        } else {
          var re = VS.utils.inflector.escapeRegExp(searchTerm || '');
          var matcher = new RegExp('\\b' + re, 'i');
          matches = $.grep(matches, function(item) {
            return matcher.test(item) ||
                   matcher.test(item.value) ||
                   matcher.test(item.label);
        });
        }
      }

      if (options.preserveOrder) {
        resp(matches);
      } else {
        resp(_.sortBy(matches, function(match) {
          if (match == value || match.value == value) return '';
          else return match;
        }));
      }
    });

  },

  // Sets the facet's model's value.
  set : function(value) {
    if (!value) return;
    this.model.set({'value': value});
  },

  // Before the searchBox performs a search, we need to close the
  // autocomplete menu.
  search : function(e, direction) {
    if (!direction) direction = 1;
    this.closeAutocomplete();
    this.options.app.searchBox.searchEvent(e);
    _.defer(_.bind(function() {
      this.options.app.searchBox.focusNextFacet(this, direction, {viewPosition: this.options.order});
    }, this));
  },

  // Begin editing the facet's input. This is called when the user enters
  // the input either from another facet or directly clicking on it.
  //
  // This method tells all other facets and inputs to disable so it can have
  // the sole focus. It also prepares the autocompletion menu.
  enableEdit : function() {
    if (this.modes.editing != 'is') {
      this.setMode('is', 'editing');
      this.deselectFacet();
      if (this.box.val() == '') {
        this.box.val(this.model.get('value'));
      }
    }

    this.flags.canClose = false;
    this.options.app.searchBox.disableFacets(this);
    this.options.app.searchBox.addFocus();
    _.defer(_.bind(function() {
      this.options.app.searchBox.addFocus();
    }, this));
    this.resize();
    this.searchAutocomplete();
    this.box.focus();
  },

  // When the user blurs the input, they may either be going to another input
  // or off the search box entirely. If they go to another input, this facet
  // will be instantly disabled, and the canClose flag will be turned back off.
  //
  // However, if the user clicks elsewhere on the page, this method starts a timer
  // that checks if any of the other inputs are selected or are being edited. If
  // not, then it can finally close itself and its autocomplete menu.
  deferDisableEdit : function() {
    this.flags.canClose = true;
    _.delay(_.bind(function() {
      if (this.flags.canClose && !this.box.is(':focus') &&
          this.modes.editing == 'is' && this.modes.selected != 'is') {
        this.disableEdit();
      }
    }, this), 250);
  },

  // Called either by other facets receiving focus or by the timer in `deferDisableEdit`,
  // this method will turn off the facet, remove any text selection, and close
  // the autocomplete menu.
  disableEdit : function() {
    var newFacetQuery = VS.utils.inflector.trim(this.box.val());
    if (newFacetQuery != this.model.get('value')) {
      this.set(newFacetQuery);
    }
    this.flags.canClose = false;
    this.box.selectRange(0, 0);
    this.box.blur();
    this.setMode('not', 'editing');
    this.closeAutocomplete();
    this.options.app.searchBox.removeFocus();
  },

  // Selects the facet, which blurs the facet's input and highlights the facet.
  // If this is the only facet being selected (and not part of a select all event),
  // we attach a mouse/keyboard watcher to check if the next action by the user
  // should delete this facet or just deselect it.
  selectFacet : function(e) {
    if (e) e.preventDefault();
    var allSelected = this.options.app.searchBox.allSelected();
    if (this.modes.selected == 'is') return;

    if (this.box.is(':focus')) {
      this.box.setCursorPosition(0);
      this.box.blur();
    }

    this.flags.canClose = false;
    this.closeAutocomplete();
    this.setMode('is', 'selected');
    this.setMode('not', 'editing');
    if (!allSelected || e) {
      $(document).unbind('keydown.facet', this.keydown);
      $(document).unbind('click.facet', this.deselectFacet);
      _.defer(_.bind(function() {
        $(document).unbind('keydown.facet').bind('keydown.facet', this.keydown);
        $(document).unbind('click.facet').one('click.facet', this.deselectFacet);
      }, this));
      this.options.app.searchBox.disableFacets(this);
      this.options.app.searchBox.addFocus();
    }
    return false;
  },

  // Turns off highlighting on the facet. Called in a variety of ways, this
  // only deselects the facet if it is selected, and then cleans up the
  // keyboard/mouse watchers that were created when the facet was first
  // selected.
  deselectFacet : function(e) {
    if (e) e.preventDefault();
    if (this.modes.selected == 'is') {
      this.setMode('not', 'selected');
      this.closeAutocomplete();
      this.options.app.searchBox.removeFocus();
    }
    $(document).unbind('keydown.facet', this.keydown);
    $(document).unbind('click.facet', this.deselectFacet);
    return false;
  },

  // Is the user currently focused in this facet's input field?
  isFocused : function() {
    return this.box.is(':focus');
  },

  // Hovering over the delete button styles the facet so the user knows that
  // the delete button will kill the entire facet.
  showDelete : function() {
    $(this.el).addClass('search_facet_maybe_delete');
  },

  // On `mouseout`, the user is no longer hovering on the delete button.
  hideDelete : function() {
    $(this.el).removeClass('search_facet_maybe_delete');
  },

  // When switching between facets, depending on the direction the cursor is
  // coming from, the cursor in this facet's input field should match the original
  // direction.
  setCursorAtEnd : function(direction) {
    if (direction == -1) {
      this.box.setCursorPosition(this.box.val().length);
    } else {
      this.box.setCursorPosition(0);
    }
  },

  // Deletes the facet and sends the cursor over to the nearest input field.
  remove : function(e) {
    var committed = this.model.get('value');
    this.deselectFacet();
    this.disableEdit();
    this.options.app.searchQuery.remove(this.model);
    if (committed && this.options.app.options.autosearch) {
      this.search(e, -1);
    } else {
      this.options.app.searchBox.renderFacets();
      this.options.app.searchBox.focusNextFacet(this, -1, {viewPosition: this.options.order});
    }
  },

  // Selects the text in the facet's input field. When the user tabs between
  // facets, convention is to highlight the entire field.
  selectText: function() {
    this.box.selectRange(0, this.box.val().length);
  },

  // Handles all keyboard inputs when in the facet's input field. This checks
  // for movement between facets and inputs, entering a new value that needs
  // to be autocompleted, as well as the removal of this facet.
  keydown : function(e) {
    var key = VS.app.hotkeys.key(e);

    if (key == 'enter' && this.box.val()) {
      this.disableEdit();
      this.search(e);
    } else if (key == 'left') {
      if (this.modes.selected == 'is') {
        this.deselectFacet();
        this.options.app.searchBox.focusNextFacet(this, -1, {startAtEnd: -1});
      } else if (this.box.getCursorPosition() == 0 && !this.box.getSelection().length) {
        this.selectFacet();
      }
    } else if (key == 'right') {
      if (this.modes.selected == 'is') {
        e.preventDefault();
        this.deselectFacet();
        this.setCursorAtEnd(0);
        this.enableEdit();
      } else if (this.box.getCursorPosition() == this.box.val().length) {
        e.preventDefault();
        this.disableEdit();
        this.options.app.searchBox.focusNextFacet(this, 1);
      }
    } else if (VS.app.hotkeys.shift && key == 'tab') {
      e.preventDefault();
      this.options.app.searchBox.focusNextFacet(this, -1, {
        startAtEnd  : -1,
        skipToFacet : true,
        selectText  : true
      });
    } else if (key == 'tab') {
      e.preventDefault();
      this.options.app.searchBox.focusNextFacet(this, 1, {
        skipToFacet : true,
        selectText  : true
      });
    } else if (VS.app.hotkeys.command && (e.which == 97 || e.which == 65)) {
      e.preventDefault();
      this.options.app.searchBox.selectAllFacets();
      return false;
    } else if (VS.app.hotkeys.printable(e) && this.modes.selected == 'is') {
      this.options.app.searchBox.focusNextFacet(this, -1, {startAtEnd: -1});
      this.remove(e);
    } else if (key == 'backspace') {
      if (this.modes.selected == 'is') {
        e.preventDefault();
        this.remove(e);
      } else if (this.box.getCursorPosition() == 0 &&
                 !this.box.getSelection().length) {
        e.preventDefault();
        this.selectFacet();
      }
    }

    // Handle paste events
    if (e.which == null) {
        // this.searchAutocomplete(e);
        _.defer(_.bind(this.resize, this, e));
    } else {
      this.resize(e);
    }
  }

});

})();
<commit_msg>Avoid browser back when holding down backspace key<commit_after>(function() {

var $ = jQuery; // Handle namespaced jQuery

// This is the visual search facet that holds the category and its autocompleted
// input field.
VS.ui.SearchFacet = Backbone.View.extend({

  type : 'facet',

  className : 'search_facet',

  events : {
    'click .category'           : 'selectFacet',
    'keydown input'             : 'keydown',
    'mousedown input'           : 'enableEdit',
    'mouseover .VS-icon-cancel' : 'showDelete',
    'mouseout .VS-icon-cancel'  : 'hideDelete',
    'click .VS-icon-cancel'     : 'remove'
  },

  initialize : function(options) {
    this.flags = {
      canClose : false
    };
    _.bindAll(this, 'set', 'keydown', 'deselectFacet', 'deferDisableEdit');
  },

  // Rendering the facet sets up autocompletion, events on blur, and populates
  // the facet's input with its starting value.
  render : function() {
    $(this.el).html(JST['search_facet']({
      model : this.model
    }));

    this.setMode('not', 'editing');
    this.setMode('not', 'selected');
    this.box = this.$('input');
    this.box.val(this.model.label());
    this.box.bind('blur', this.deferDisableEdit);
    // Handle paste events with `propertychange`
    this.box.bind('input propertychange', this.keydown);
    this.setupAutocomplete();

    return this;
  },

  // This method is used to setup the facet's input to auto-grow.
  // This is defered in the searchBox so it can be attached to the
  // DOM to get the correct font-size.
  calculateSize : function() {
    this.box.autoGrowInput();
    this.box.unbind('updated.autogrow');
    this.box.bind('updated.autogrow', _.bind(this.moveAutocomplete, this));
  },

  // Forces a recalculation of this facet's input field's value. Called when
  // the facet is focused, removed, or otherwise modified.
  resize : function(e) {
    this.box.trigger('resize.autogrow', e);
  },

  // Watches the facet's input field to see if it matches the beginnings of
  // words in `autocompleteValues`, which is different for every category.
  // If the value, when selected from the autocompletion menu, is different
  // than what it was, commit the facet and search for it.
  setupAutocomplete : function() {
    this.box.autocomplete({
      source    : _.bind(this.autocompleteValues, this),
      minLength : 0,
      delay     : 0,
      autoFocus : true,
      position  : {offset : "0 5"},
      create    : _.bind(function(e, ui) {
        $(this.el).find('.ui-autocomplete-input').css('z-index','auto');
      }, this),
      select    : _.bind(function(e, ui) {
        e.preventDefault();
        var originalValue = this.model.get('value');
        this.set(ui.item.value);
        if (originalValue != ui.item.value || this.box.val() != ui.item.value) {
          if (this.options.app.options.autosearch) {
            this.search(e);
          } else {
              this.options.app.searchBox.renderFacets();
              this.options.app.searchBox.focusNextFacet(this, 1, {viewPosition: this.options.order});
          }
        }
        return false;
      }, this),
      open      : _.bind(function(e, ui) {
        var box = this.box;
        this.box.autocomplete('widget').find('.ui-menu-item').each(function() {
          var $value = $(this),
              autoCompleteData = $value.data('item.autocomplete') || $value.data('ui-autocomplete-item');

          if (autoCompleteData['value'] == box.val() && box.data('uiAutocomplete').menu.activate) {
            box.data('uiAutocomplete').menu.activate(new $.Event("mouseover"), $value);
          }
        });
      }, this)
    });

    this.box.autocomplete('widget').addClass('VS-interface');
  },

  // As the facet's input field grows, it may move to the next line in the
  // search box. `autoGrowInput` triggers an `updated` event on the input
  // field, which is bound to this method to move the autocomplete menu.
  moveAutocomplete : function() {
    var autocomplete = this.box.data('uiAutocomplete');
    if (autocomplete) {
      autocomplete.menu.element.position({
        my        : "left top",
        at        : "left bottom",
        of        : this.box.data('uiAutocomplete').element,
        collision : "flip",
        offset    : "0 5"
      });
    }
  },

  // When a user enters a facet and it is being edited, immediately show
  // the autocomplete menu and size it to match the contents.
  searchAutocomplete : function(e) {
    var autocomplete = this.box.data('uiAutocomplete');
    if (autocomplete) {
      var menu = autocomplete.menu.element;
      autocomplete.search();

      // Resize the menu based on the correctly measured width of what's bigger:
      // the menu's original size or the menu items' new size.
      menu.outerWidth(Math.max(
        menu.width('').outerWidth(),
        autocomplete.element.outerWidth()
      ));
    }
  },

  // Closes the autocomplete menu. Called on disabling, selecting, deselecting,
  // and anything else that takes focus out of the facet's input field.
  closeAutocomplete : function() {
    var autocomplete = this.box.data('uiAutocomplete');
    if (autocomplete) autocomplete.close();
  },

  // Search terms used in the autocomplete menu. These are specific to the facet,
  // and only match for the facet's category. The values are then matched on the
  // first letter of any word in matches, and finally sorted according to the
  // value's own category. You can pass `preserveOrder` as an option in the
  // `facetMatches` callback to skip any further ordering done client-side.
  autocompleteValues : function(req, resp) {
    var category = this.model.get('category');
    var value    = this.model.get('value');
    var searchTerm = req.term;

    this.options.app.options.callbacks.valueMatches(category, searchTerm, function(matches, options) {
      options = options || {};
      matches = matches || [];

      if (searchTerm && value != searchTerm) {
        if (options.preserveMatches) {
          resp(matches);
        } else {
          var re = VS.utils.inflector.escapeRegExp(searchTerm || '');
          var matcher = new RegExp('\\b' + re, 'i');
          matches = $.grep(matches, function(item) {
            return matcher.test(item) ||
                   matcher.test(item.value) ||
                   matcher.test(item.label);
        });
        }
      }

      if (options.preserveOrder) {
        resp(matches);
      } else {
        resp(_.sortBy(matches, function(match) {
          if (match == value || match.value == value) return '';
          else return match;
        }));
      }
    });

  },

  // Sets the facet's model's value.
  set : function(value) {
    if (!value) return;
    this.model.set({'value': value});
  },

  // Before the searchBox performs a search, we need to close the
  // autocomplete menu.
  search : function(e, direction) {
    if (!direction) direction = 1;
    this.closeAutocomplete();
    this.options.app.searchBox.searchEvent(e);
    _.defer(_.bind(function() {
      this.options.app.searchBox.focusNextFacet(this, direction, {viewPosition: this.options.order});
    }, this));
  },

  // Begin editing the facet's input. This is called when the user enters
  // the input either from another facet or directly clicking on it.
  //
  // This method tells all other facets and inputs to disable so it can have
  // the sole focus. It also prepares the autocompletion menu.
  enableEdit : function() {
    if (this.modes.editing != 'is') {
      this.setMode('is', 'editing');
      this.deselectFacet();
      if (this.box.val() == '') {
        this.box.val(this.model.get('value'));
      }
    }

    this.flags.canClose = false;
    this.options.app.searchBox.disableFacets(this);
    this.options.app.searchBox.addFocus();
    _.defer(_.bind(function() {
      this.options.app.searchBox.addFocus();
    }, this));
    this.resize();
    this.searchAutocomplete();
    this.box.focus();
  },

  // When the user blurs the input, they may either be going to another input
  // or off the search box entirely. If they go to another input, this facet
  // will be instantly disabled, and the canClose flag will be turned back off.
  //
  // However, if the user clicks elsewhere on the page, this method starts a timer
  // that checks if any of the other inputs are selected or are being edited. If
  // not, then it can finally close itself and its autocomplete menu.
  deferDisableEdit : function() {
    this.flags.canClose = true;
    _.delay(_.bind(function() {
      if (this.flags.canClose && !this.box.is(':focus') &&
          this.modes.editing == 'is' && this.modes.selected != 'is') {
        this.disableEdit();
      }
    }, this), 250);
  },

  // Called either by other facets receiving focus or by the timer in `deferDisableEdit`,
  // this method will turn off the facet, remove any text selection, and close
  // the autocomplete menu.
  disableEdit : function() {
    var newFacetQuery = VS.utils.inflector.trim(this.box.val());
    if (newFacetQuery != this.model.get('value')) {
      this.set(newFacetQuery);
    }
    this.flags.canClose = false;
    this.box.selectRange(0, 0);
    this.box.blur();
    this.setMode('not', 'editing');
    this.closeAutocomplete();
    this.options.app.searchBox.removeFocus();
  },

  // Selects the facet, which blurs the facet's input and highlights the facet.
  // If this is the only facet being selected (and not part of a select all event),
  // we attach a mouse/keyboard watcher to check if the next action by the user
  // should delete this facet or just deselect it.
  selectFacet : function(e) {
    if (e) e.preventDefault();
    var allSelected = this.options.app.searchBox.allSelected();
    if (this.modes.selected == 'is') return;

    if (this.box.is(':focus')) {
      this.box.setCursorPosition(0);
      this.box.blur();
    }

    this.flags.canClose = false;
    this.closeAutocomplete();
    this.setMode('is', 'selected');
    this.setMode('not', 'editing');
    if (!allSelected || e) {
      $(document).unbind('keydown.facet', this.keydown);
      $(document).unbind('click.facet', this.deselectFacet);
      _.defer(_.bind(function() {
        $(document).unbind('keydown.facet').bind('keydown.facet', this.keydown);
        $(document).unbind('click.facet').one('click.facet', this.deselectFacet);
      }, this));
      this.options.app.searchBox.disableFacets(this);
      this.options.app.searchBox.addFocus();
    }
    return false;
  },

  // Turns off highlighting on the facet. Called in a variety of ways, this
  // only deselects the facet if it is selected, and then cleans up the
  // keyboard/mouse watchers that were created when the facet was first
  // selected.
  deselectFacet : function(e) {
    if (e) e.preventDefault();
    if (this.modes.selected == 'is') {
      this.setMode('not', 'selected');
      this.closeAutocomplete();
      this.options.app.searchBox.removeFocus();
    }
    $(document).unbind('keydown.facet', this.keydown);
    $(document).unbind('click.facet', this.deselectFacet);
    return false;
  },

  // Is the user currently focused in this facet's input field?
  isFocused : function() {
    return this.box.is(':focus');
  },

  // Hovering over the delete button styles the facet so the user knows that
  // the delete button will kill the entire facet.
  showDelete : function() {
    $(this.el).addClass('search_facet_maybe_delete');
  },

  // On `mouseout`, the user is no longer hovering on the delete button.
  hideDelete : function() {
    $(this.el).removeClass('search_facet_maybe_delete');
  },

  // When switching between facets, depending on the direction the cursor is
  // coming from, the cursor in this facet's input field should match the original
  // direction.
  setCursorAtEnd : function(direction) {
    if (direction == -1) {
      this.box.setCursorPosition(this.box.val().length);
    } else {
      this.box.setCursorPosition(0);
    }
  },

  // Deletes the facet and sends the cursor over to the nearest input field.
  remove : function(e) {
    var committed = this.model.get('value');
    this.deselectFacet();
    this.disableEdit();
    this.options.app.searchQuery.remove(this.model);
    if (committed && this.options.app.options.autosearch) {
      this.search(e, -1);
    } else {
      this.options.app.searchBox.renderFacets();
      this.options.app.searchBox.focusNextFacet(this, -1, {viewPosition: this.options.order});
    }
  },

  // Selects the text in the facet's input field. When the user tabs between
  // facets, convention is to highlight the entire field.
  selectText: function() {
    this.box.selectRange(0, this.box.val().length);
  },

  // Handles all keyboard inputs when in the facet's input field. This checks
  // for movement between facets and inputs, entering a new value that needs
  // to be autocompleted, as well as the removal of this facet.
  keydown : function(e) {
    var key = VS.app.hotkeys.key(e);

    if (key == 'enter' && this.box.val()) {
      this.disableEdit();
      this.search(e);
    } else if (key == 'left') {
      if (this.modes.selected == 'is') {
        this.deselectFacet();
        this.options.app.searchBox.focusNextFacet(this, -1, {startAtEnd: -1});
      } else if (this.box.getCursorPosition() == 0 && !this.box.getSelection().length) {
        this.selectFacet();
      }
    } else if (key == 'right') {
      if (this.modes.selected == 'is') {
        e.preventDefault();
        this.deselectFacet();
        this.setCursorAtEnd(0);
        this.enableEdit();
      } else if (this.box.getCursorPosition() == this.box.val().length) {
        e.preventDefault();
        this.disableEdit();
        this.options.app.searchBox.focusNextFacet(this, 1);
      }
    } else if (VS.app.hotkeys.shift && key == 'tab') {
      e.preventDefault();
      this.options.app.searchBox.focusNextFacet(this, -1, {
        startAtEnd  : -1,
        skipToFacet : true,
        selectText  : true
      });
    } else if (key == 'tab') {
      e.preventDefault();
      this.options.app.searchBox.focusNextFacet(this, 1, {
        skipToFacet : true,
        selectText  : true
      });
    } else if (VS.app.hotkeys.command && (e.which == 97 || e.which == 65)) {
      e.preventDefault();
      this.options.app.searchBox.selectAllFacets();
      return false;
    } else if (VS.app.hotkeys.printable(e) && this.modes.selected == 'is') {
      this.options.app.searchBox.focusNextFacet(this, -1, {startAtEnd: -1});
      this.remove(e);
    } else if (key == 'backspace') {
       $(document).on('keydown.backspace', function(e) {
          if (VS.app.hotkeys.key(e) === 'backspace') {
             e.preventDefault();
          }
       });

       $(document).on('keyup.backspace', function(e) {
          $(document).off('.backspace');
       });

      if (this.modes.selected == 'is') {
        e.preventDefault();
        this.remove(e);
      } else if (this.box.getCursorPosition() == 0 &&
                 !this.box.getSelection().length) {
        e.preventDefault();
        this.selectFacet();
      }
       e.stopPropagation();
    }

    // Handle paste events
    if (e.which == null) {
        // this.searchAutocomplete(e);
        _.defer(_.bind(this.resize, this, e));
    } else {
      this.resize(e);
    }
  }

});

})();
<|endoftext|>


<commit_before>(function(app) {
  'use strict';

  var helper = app.helper || require('./helper.js');

  var dom = {};

  dom.unsupported = function() {
    return (typeof document === 'undefined');
  };

  dom.global = function(key, value) {
    return (typeof module !== 'undefined' && module.exports) ? global : window;
  };

  dom.el = function(selector) {
    if (selector.charAt(0) === '<') {
      selector = selector.match(/<(.+)>/)[1];
      return document.createElement(selector);
    }
    return document.querySelector(selector);
  };

  dom.body = function() {
    return document.body;
  };

  dom.append = function(parent, el) {
    parent.appendChild(el);
  };

  dom.remove = function(el) {
    var parentNode = el.parentNode;
    if (parentNode)
      parentNode.removeChild(el);
  };

  dom.child = function(el, index) {
    var len = arguments.length;

    if (len === 2)
      return el.children[index];

    for (var i = 1; i < len; i++) {
      index = arguments[i];
      el = el.children[index];
    }

    return el;
  };

  dom.childNode = function(el, index) {
    var len = arguments.length;

    if (len === 2)
      return el.childNodes[index];

    for (var i = 1; i < len; i++) {
      index = arguments[i];
      el = el.childNodes[index];
    }

    return el;
  };

  dom.children = function(el) {
    return Array.prototype.slice.call(el.children);
  };

  dom.parent = function(el) {
    return el.parentNode;
  };

  dom.contains = function(el, other) {
    return el.contains(other);
  };

  dom.attr = function(el, props) {
    for (var key in props) {
      el.setAttribute(key, props[key]);
    }
  };

  dom.css = function(el, props) {
    var style = el.style;

    for (var key in props) {
      style[key] = props[key];
    }
  };

  dom.addClass = function(el, className) {
    el.classList.add(className);
  };

  dom.removeClass = function(el, className) {
    el.classList.remove(className);
  };

  dom.toggleClass = function(el, className, force) {
    if (typeof force === 'undefined') {
      el.classList.toggle(className);
      return;
    }
    if (force)
      el.classList.add(className);
    else
      el.classList.remove(className);
  };

  dom.hasClass = function(el, className) {
    return el.classList.contains(className);
  };

  dom.data = function(el, key, value) {
    if (typeof value === 'undefined')
      return el.dataset[key];

    if (value === null) {
      if (key in el.dataset)
        delete el.dataset[key];
      return;
    }

    el.dataset[key] = value;
  };

  dom.name = function(el, s) {
    el.name = s;
  };

  dom.text = function(el, s) {
    el.textContent = s;
  };

  dom.html = function(el, s) {
    el.innerHTML = s;
  };

  dom.value = function(el, s) {
    if (typeof s === 'undefined')
      return el.value;

    el.value = s;
  };

  dom.rect = function(el) {
    return el.getBoundingClientRect();
  };

  dom.offsetHeight = function(el) {
    return el.offsetHeight;
  };

  dom.scrollLeft = function(el) {
    return el.scrollLeft;
  };

  dom.scrollTop = function(el) {
    return el.scrollTop;
  };

  dom.contentWindow = function(iframe) {
    return iframe.contentWindow;
  };

  dom.writeContent = function(iframe, s) {
    var doc = iframe.contentDocument;
    doc.open();
    doc.write(s);
    doc.close();
  };

  dom.fillContentHeight = function(iframe) {
    iframe.style.height = iframe.contentDocument.documentElement.scrollHeight + 'px';
  };

  dom.removeFocus = function() {
    document.activeElement.blur();
  };

  dom.sort = function(el) {
    helper.sortBy(dom.children(el), 'textContent').forEach(function(child) {
      dom.append(el, child);
    });
  };

  dom.animate = function(callback) {
    return window.requestAnimationFrame(callback);
  };

  dom.on = function(el, type, listener, useCapture) {
    el.addEventListener(type, listener, !!useCapture);
  };

  dom.off = function(el, type, listener, useCapture) {
    el.removeEventListener(type, listener, !!useCapture);
  };

  dom.supportsTouch = function() {
    return 'createTouch' in document;
  };

  dom.target = function(event) {
    if (dom.supportsTouch() && 'changedTouches' in event) {
      event = event.changedTouches[0];
      return document.elementFromPoint(event.clientX, event.clientY);
    }

    return event.target;
  };

  dom.cancel = function(event) {
    event.preventDefault();
  };

  dom.eventType = function(name) {
    var supportsTouch = dom.supportsTouch();

    switch (name) {
    case 'start':
      return (supportsTouch ? 'touchstart' : 'mousedown');
    case 'move':
      return (supportsTouch ? 'touchmove' : 'mousemove');
    case 'end':
      return (supportsTouch ? 'touchend' : 'mouseup');
    }
  };

  dom.pagePoint = function(event, offset) {
    if (dom.supportsTouch())
      event = event.changedTouches[0];

    return {
      x: event.pageX - (offset ? offset.x : 0),
      y: event.pageY - (offset ? offset.y : 0)
    };
  };

  dom.clientPoint = function(event, offset) {
    if (dom.supportsTouch())
      event = event.changedTouches[0];

    return {
      x: event.clientX - (offset ? offset.x : 0),
      y: event.clientY - (offset ? offset.y : 0)
    };
  };

  dom.identifier = function(event) {
    if (dom.supportsTouch())
      return event.changedTouches[0].identifier;

    return null;
  };

  dom.Draggable = (function() {
    var Draggable = function(props) {
      this.element = props.element;
      this.onstart = props.onstart;
      this.onmove = props.onmove;
      this.onend = props.onend;
      this.start = start.bind(this);
      this.move = move.bind(this);
      this.end = end.bind(this);
      this.lock = false;
      this.identifier = null;
      this.startingPoint = null;

      dom.on(this.element, dom.eventType('start'), this.start);
    };

    Draggable.prototype.destroy = function() {
      dom.off(this.element, dom.eventType('start'), this.start);
      dom.off(document, dom.eventType('move'), this.move);
      dom.off(document, dom.eventType('end'), this.end);
    };

    var start = function(event) {
      if (this.lock)
        return;

      this.lock = true;
      this.identifier = dom.identifier(event);
      this.startingPoint = dom.pagePoint(event);

      var element = this.element;
      var onstart = this.onstart;

      var rect = dom.rect(element);
      var p = dom.clientPoint(event, {
        x: rect.left - dom.scrollLeft(element),
        y: rect.top - dom.scrollTop(element)
      });

      if (typeof onstart === 'function')
        onstart(p.x, p.y, event);

      dom.on(document, dom.eventType('move'), this.move);
      dom.on(document, dom.eventType('end'), this.end);
    };

    var move = function(event) {
      var identifier = this.identifier;

      if (identifier && identifier !== dom.identifier(event))
        return;

      var onmove = this.onmove;
      var d = dom.pagePoint(event, this.startingPoint);

      if (typeof onmove === 'function')
        onmove(d.x, d.y, event);
    };

    var end = function(event) {
      var identifier = this.identifier;

      if (identifier && identifier !== dom.identifier(event))
        return;

      dom.off(document, dom.eventType('move'), this.move);
      dom.off(document, dom.eventType('end'), this.end);

      var onend = this.onend;
      var d = dom.pagePoint(event, this.startingPoint);

      if (typeof onend === 'function')
        onend(d.x, d.y, event);

      this.lock = false;
    };

    return Draggable;
  })();

  dom.ajax = function(opt) {
    var type = opt.type;
    var url = opt.url;

    return new Promise(function(resolve, reject) {
      var req = new XMLHttpRequest();

      var onfailed = function() {
        reject(new Error('Failed to load resource: ' + type + ' ' + url));
      };

      req.onload = function() {
        if (req.status >= 200 && req.status < 400)
          resolve(req.responseText);
        else
          onfailed();
      };

      req.onerror = onfailed;
      req.onabort = onfailed;

      req.open(type, url, true);
      req.send();
    });
  };

  dom.origin = function() {
    return location.protocol + '//' + location.host;
  };

  if (typeof module !== 'undefined' && module.exports)
    module.exports = dom;
  else
    app.dom = dom;
})(this.app || (this.app = {}));

(function(global) {
  'use strict';
  var isTouchEnabled = 'createTouch' in document;
  var START = isTouchEnabled ? 'touchstart' : 'mousedown';
  var MOVE = isTouchEnabled ? 'touchmove' : 'mousemove';
  var END = isTouchEnabled ? 'touchend' : 'mouseup';

  var cancelEvent = function(event) {
    event.preventDefault();
    event.stopPropagation();
  };

  var pointerEvent = (function() {
    var isInitialized = false;
    var lock = false;
    var optionMap = {};
    var noop = function() {};
    var previousTarget;
    var currentTarget;
    var currentOptions;
    var startOffset;
    var isDragStarted;
    var holdTimerId = null;

    var extend = function(a, b) {
      for (var key in b) {
        a[key] = b[key];
      }
      return a;
    };

    var getTarget = function(event) {
      if (isTouchEnabled) {
        var touch = event.changedTouches[0];
        return document.elementFromPoint(touch.clientX, touch.clientY);
      } else {
        return event.target;
      }
    };

    var getCurrentOptions = function(optionMap, target) {
      var options = [];

      var optionListById = optionMap['#' + target.id];
      if (Array.isArray(optionListById))
        options = options.concat(optionListById);

      var classList = target.classList;
      for (var i = 0, len = classList.length; i < len; i++) {
        var className = classList[i];
        var optionListByClassName = optionMap['.' + className];
        if (Array.isArray(optionListByClassName))
          options = options.concat(optionListByClassName);
      }

      return options;
    };

    var getStartOffset = function(event) {
      event = isTouchEnabled ? event.changedTouches[0] : event;
      return {
        x: event.pageX,
        y: event.pageY
      };
    };

    var getOffset = function(event, startOffset) {
      event = isTouchEnabled ? event.changedTouches[0] : event;
      return {
        x: event.pageX - startOffset.x,
        y: event.pageY - startOffset.y
      };
    };

    var holdTimer = function() {
      holdTimerId = null;
      currentOptions.forEach(function(option) {
        option.onhold();
      });
    };

    var start = function(event) {
      if (lock)
        return;

      lock = true;
      currentTarget = previousTarget = getTarget(event);
      currentOptions = getCurrentOptions(optionMap, currentTarget);
      startOffset = getStartOffset(event);
      isDragStarted = false;

      var hasHoldEvent = currentOptions.some(function(option) {
        return option.onhold !== noop;
      });

      if (hasHoldEvent)
        holdTimerId = setTimeout(holdTimer, 300);

      document.addEventListener(MOVE, move);
      document.addEventListener(END, end);

      currentOptions.forEach(function(option) {
        option.onstart(event);
      });
    };

    var move = function(event) {
      var target = getTarget(event);
      var offset = getOffset(event, startOffset);

      if (holdTimerId !== null && (Math.abs(offset.x) > 5 || Math.abs(offset.y) > 5)) {
        clearTimeout(holdTimerId);
        holdTimerId = null;
      }

      currentOptions.forEach(function(option) {
        if (!isDragStarted)
          option.ondragstart(event);

        option.ondrag(event, offset.x, offset.y);

        if (target !== previousTarget) {
          if (previousTarget === currentTarget)
            option.onout(event);
          else if (target === currentTarget)
            option.onover(event);
        }
      });

      previousTarget = target;

      if (!isDragStarted)
        isDragStarted = true;
    };

    var end = function(event) {
      document.removeEventListener(MOVE, move);
      document.removeEventListener(END, end);

      var target = getTarget(event);
      var offset = getOffset(event, startOffset);

      currentOptions.forEach(function(option) {
        if (isDragStarted)
          option.ondragend(event, offset.x, offset.y);

        option.onend(event);

        if (target === currentTarget)
          option.ontap(event);
      });

      if (holdTimerId !== null) {
        clearTimeout(holdTimerId);
        holdTimerId = null;
      }

      lock = false;
    };

    return function(selector, option) {
      if (!isInitialized) {
        document.addEventListener(START, start);
        isInitialized = true;
      }

      var options = optionMap[selector];

      if (!options)
        options = optionMap[selector] = [];

      options.push(extend({
        onstart: noop,
        onhold: noop,
        ondragstart: noop,
        ondrag: noop,
        onout: noop,
        onover: noop,
        ondragend: noop,
        onend: noop,
        ontap: noop
      }, option));
    };
  })();

  function startDragEvent(event, option) {
    var startOffset;
    function moveListener(event) {
      cancelEvent(event);
      event = isTouchEnabled ? event.touches[0] : event;
      var dx = event.pageX - startOffset.left;
      var dy = event.pageY - startOffset.top;
      if (typeof option.drag === 'function')
        option.drag(dx, dy);
    }
    function endListener(event) {
      cancelEvent(event);
      if (isTouchEnabled && event.touches.length > 0)
        return;
      event = isTouchEnabled ? event.changedTouches[0] : event;
      document.removeEventListener(MOVE, moveListener, false);
      document.removeEventListener(END, endListener, false);
      var dx = event.pageX - startOffset.left;
      var dy = event.pageY - startOffset.top;
      if (typeof option.end === 'function')
        option.end(dx, dy);
    }
    cancelEvent(event);
    event = isTouchEnabled ? event.touches[0] : event;
    startOffset = {left: event.pageX, top: event.pageY};
    document.addEventListener(MOVE, moveListener, false);
    document.addEventListener(END, endListener, false);
  }

  function hasClass(el, className) {
    return el.classList.contains(className);
  }

  function addClass(el, className) {
    el.classList.add(className);
  }

  function removeClass(el, className) {
    el.classList.remove(className);
  }

  function indexOf(parentNode, childNode) {
    return Array.prototype.indexOf.call(parentNode.children, childNode);
  }

  function setCursor(value) {
    document.body.style.cursor = value;
  }

  function removeKeyboardFocus() {
    var activeElement = document.activeElement;
    if (activeElement && activeElement.blur)
      activeElement.blur();
  }

  function translate(el, x, y) {
    var value = 'translate(' + x + 'px, ' + y + 'px)';
    el.style.transform =  value;
    el.style.webkitTransform = value;
    el.style.MozTransform = value;
    el.style.msTransform = value;
    el.style.OTransform = value;
  }

  function requestAnimationFrame(callback) {
    return window.requestAnimationFrame(callback);
  }

  function supportsTouch() {
    return isTouchEnabled;
  }

  global.dom = {
    pointerEvent: pointerEvent,
    startDragEvent: startDragEvent,
    hasClass: hasClass,
    addClass: addClass,
    removeClass: removeClass,
    indexOf: indexOf,
    setCursor: setCursor,
    removeKeyboardFocus: removeKeyboardFocus,
    translate: translate,
    requestAnimationFrame: requestAnimationFrame,
    supportsTouch: supportsTouch
  };
})(this);<commit_msg>Add utility method to disable an element<commit_after>(function(app) {
  'use strict';

  var helper = app.helper || require('./helper.js');

  var dom = {};

  dom.unsupported = function() {
    return (typeof document === 'undefined');
  };

  dom.global = function(key, value) {
    return (typeof module !== 'undefined' && module.exports) ? global : window;
  };

  dom.el = function(selector) {
    if (selector.charAt(0) === '<') {
      selector = selector.match(/<(.+)>/)[1];
      return document.createElement(selector);
    }
    return document.querySelector(selector);
  };

  dom.body = function() {
    return document.body;
  };

  dom.append = function(parent, el) {
    parent.appendChild(el);
  };

  dom.remove = function(el) {
    var parentNode = el.parentNode;
    if (parentNode)
      parentNode.removeChild(el);
  };

  dom.child = function(el, index) {
    var len = arguments.length;

    if (len === 2)
      return el.children[index];

    for (var i = 1; i < len; i++) {
      index = arguments[i];
      el = el.children[index];
    }

    return el;
  };

  dom.childNode = function(el, index) {
    var len = arguments.length;

    if (len === 2)
      return el.childNodes[index];

    for (var i = 1; i < len; i++) {
      index = arguments[i];
      el = el.childNodes[index];
    }

    return el;
  };

  dom.children = function(el) {
    return Array.prototype.slice.call(el.children);
  };

  dom.parent = function(el) {
    return el.parentNode;
  };

  dom.contains = function(el, other) {
    return el.contains(other);
  };

  dom.attr = function(el, props) {
    for (var key in props) {
      el.setAttribute(key, props[key]);
    }
  };

  dom.css = function(el, props) {
    var style = el.style;

    for (var key in props) {
      style[key] = props[key];
    }
  };

  dom.addClass = function(el, className) {
    el.classList.add(className);
  };

  dom.removeClass = function(el, className) {
    el.classList.remove(className);
  };

  dom.toggleClass = function(el, className, force) {
    if (typeof force === 'undefined') {
      el.classList.toggle(className);
      return;
    }
    if (force)
      el.classList.add(className);
    else
      el.classList.remove(className);
  };

  dom.hasClass = function(el, className) {
    return el.classList.contains(className);
  };

  dom.data = function(el, key, value) {
    if (typeof value === 'undefined')
      return el.dataset[key];

    if (value === null) {
      if (key in el.dataset)
        delete el.dataset[key];
      return;
    }

    el.dataset[key] = value;
  };

  dom.name = function(el, s) {
    el.name = s;
  };

  dom.text = function(el, s) {
    el.textContent = s;
  };

  dom.html = function(el, s) {
    el.innerHTML = s;
  };

  dom.value = function(el, s) {
    if (typeof s === 'undefined')
      return el.value;

    el.value = s;
  };

  dom.disabled = function(el, disabled) {
    el.disabled = disabled;
  };

  dom.rect = function(el) {
    return el.getBoundingClientRect();
  };

  dom.offsetHeight = function(el) {
    return el.offsetHeight;
  };

  dom.scrollLeft = function(el) {
    return el.scrollLeft;
  };

  dom.scrollTop = function(el) {
    return el.scrollTop;
  };

  dom.contentWindow = function(iframe) {
    return iframe.contentWindow;
  };

  dom.writeContent = function(iframe, s) {
    var doc = iframe.contentDocument;
    doc.open();
    doc.write(s);
    doc.close();
  };

  dom.fillContentHeight = function(iframe) {
    iframe.style.height = iframe.contentDocument.documentElement.scrollHeight + 'px';
  };

  dom.removeFocus = function() {
    document.activeElement.blur();
  };

  dom.sort = function(el) {
    helper.sortBy(dom.children(el), 'textContent').forEach(function(child) {
      dom.append(el, child);
    });
  };

  dom.animate = function(callback) {
    return window.requestAnimationFrame(callback);
  };

  dom.on = function(el, type, listener, useCapture) {
    el.addEventListener(type, listener, !!useCapture);
  };

  dom.off = function(el, type, listener, useCapture) {
    el.removeEventListener(type, listener, !!useCapture);
  };

  dom.supportsTouch = function() {
    return 'createTouch' in document;
  };

  dom.target = function(event) {
    if (dom.supportsTouch() && 'changedTouches' in event) {
      event = event.changedTouches[0];
      return document.elementFromPoint(event.clientX, event.clientY);
    }

    return event.target;
  };

  dom.cancel = function(event) {
    event.preventDefault();
  };

  dom.eventType = function(name) {
    var supportsTouch = dom.supportsTouch();

    switch (name) {
    case 'start':
      return (supportsTouch ? 'touchstart' : 'mousedown');
    case 'move':
      return (supportsTouch ? 'touchmove' : 'mousemove');
    case 'end':
      return (supportsTouch ? 'touchend' : 'mouseup');
    }
  };

  dom.pagePoint = function(event, offset) {
    if (dom.supportsTouch())
      event = event.changedTouches[0];

    return {
      x: event.pageX - (offset ? offset.x : 0),
      y: event.pageY - (offset ? offset.y : 0)
    };
  };

  dom.clientPoint = function(event, offset) {
    if (dom.supportsTouch())
      event = event.changedTouches[0];

    return {
      x: event.clientX - (offset ? offset.x : 0),
      y: event.clientY - (offset ? offset.y : 0)
    };
  };

  dom.identifier = function(event) {
    if (dom.supportsTouch())
      return event.changedTouches[0].identifier;

    return null;
  };

  dom.Draggable = (function() {
    var Draggable = function(props) {
      this.element = props.element;
      this.onstart = props.onstart;
      this.onmove = props.onmove;
      this.onend = props.onend;
      this.start = start.bind(this);
      this.move = move.bind(this);
      this.end = end.bind(this);
      this.lock = false;
      this.identifier = null;
      this.startingPoint = null;

      dom.on(this.element, dom.eventType('start'), this.start);
    };

    Draggable.prototype.destroy = function() {
      dom.off(this.element, dom.eventType('start'), this.start);
      dom.off(document, dom.eventType('move'), this.move);
      dom.off(document, dom.eventType('end'), this.end);
    };

    var start = function(event) {
      if (this.lock)
        return;

      this.lock = true;
      this.identifier = dom.identifier(event);
      this.startingPoint = dom.pagePoint(event);

      var element = this.element;
      var onstart = this.onstart;

      var rect = dom.rect(element);
      var p = dom.clientPoint(event, {
        x: rect.left - dom.scrollLeft(element),
        y: rect.top - dom.scrollTop(element)
      });

      if (typeof onstart === 'function')
        onstart(p.x, p.y, event);

      dom.on(document, dom.eventType('move'), this.move);
      dom.on(document, dom.eventType('end'), this.end);
    };

    var move = function(event) {
      var identifier = this.identifier;

      if (identifier && identifier !== dom.identifier(event))
        return;

      var onmove = this.onmove;
      var d = dom.pagePoint(event, this.startingPoint);

      if (typeof onmove === 'function')
        onmove(d.x, d.y, event);
    };

    var end = function(event) {
      var identifier = this.identifier;

      if (identifier && identifier !== dom.identifier(event))
        return;

      dom.off(document, dom.eventType('move'), this.move);
      dom.off(document, dom.eventType('end'), this.end);

      var onend = this.onend;
      var d = dom.pagePoint(event, this.startingPoint);

      if (typeof onend === 'function')
        onend(d.x, d.y, event);

      this.lock = false;
    };

    return Draggable;
  })();

  dom.ajax = function(opt) {
    var type = opt.type;
    var url = opt.url;

    return new Promise(function(resolve, reject) {
      var req = new XMLHttpRequest();

      var onfailed = function() {
        reject(new Error('Failed to load resource: ' + type + ' ' + url));
      };

      req.onload = function() {
        if (req.status >= 200 && req.status < 400)
          resolve(req.responseText);
        else
          onfailed();
      };

      req.onerror = onfailed;
      req.onabort = onfailed;

      req.open(type, url, true);
      req.send();
    });
  };

  dom.origin = function() {
    return location.protocol + '//' + location.host;
  };

  if (typeof module !== 'undefined' && module.exports)
    module.exports = dom;
  else
    app.dom = dom;
})(this.app || (this.app = {}));

(function(global) {
  'use strict';
  var isTouchEnabled = 'createTouch' in document;
  var START = isTouchEnabled ? 'touchstart' : 'mousedown';
  var MOVE = isTouchEnabled ? 'touchmove' : 'mousemove';
  var END = isTouchEnabled ? 'touchend' : 'mouseup';

  var cancelEvent = function(event) {
    event.preventDefault();
    event.stopPropagation();
  };

  var pointerEvent = (function() {
    var isInitialized = false;
    var lock = false;
    var optionMap = {};
    var noop = function() {};
    var previousTarget;
    var currentTarget;
    var currentOptions;
    var startOffset;
    var isDragStarted;
    var holdTimerId = null;

    var extend = function(a, b) {
      for (var key in b) {
        a[key] = b[key];
      }
      return a;
    };

    var getTarget = function(event) {
      if (isTouchEnabled) {
        var touch = event.changedTouches[0];
        return document.elementFromPoint(touch.clientX, touch.clientY);
      } else {
        return event.target;
      }
    };

    var getCurrentOptions = function(optionMap, target) {
      var options = [];

      var optionListById = optionMap['#' + target.id];
      if (Array.isArray(optionListById))
        options = options.concat(optionListById);

      var classList = target.classList;
      for (var i = 0, len = classList.length; i < len; i++) {
        var className = classList[i];
        var optionListByClassName = optionMap['.' + className];
        if (Array.isArray(optionListByClassName))
          options = options.concat(optionListByClassName);
      }

      return options;
    };

    var getStartOffset = function(event) {
      event = isTouchEnabled ? event.changedTouches[0] : event;
      return {
        x: event.pageX,
        y: event.pageY
      };
    };

    var getOffset = function(event, startOffset) {
      event = isTouchEnabled ? event.changedTouches[0] : event;
      return {
        x: event.pageX - startOffset.x,
        y: event.pageY - startOffset.y
      };
    };

    var holdTimer = function() {
      holdTimerId = null;
      currentOptions.forEach(function(option) {
        option.onhold();
      });
    };

    var start = function(event) {
      if (lock)
        return;

      lock = true;
      currentTarget = previousTarget = getTarget(event);
      currentOptions = getCurrentOptions(optionMap, currentTarget);
      startOffset = getStartOffset(event);
      isDragStarted = false;

      var hasHoldEvent = currentOptions.some(function(option) {
        return option.onhold !== noop;
      });

      if (hasHoldEvent)
        holdTimerId = setTimeout(holdTimer, 300);

      document.addEventListener(MOVE, move);
      document.addEventListener(END, end);

      currentOptions.forEach(function(option) {
        option.onstart(event);
      });
    };

    var move = function(event) {
      var target = getTarget(event);
      var offset = getOffset(event, startOffset);

      if (holdTimerId !== null && (Math.abs(offset.x) > 5 || Math.abs(offset.y) > 5)) {
        clearTimeout(holdTimerId);
        holdTimerId = null;
      }

      currentOptions.forEach(function(option) {
        if (!isDragStarted)
          option.ondragstart(event);

        option.ondrag(event, offset.x, offset.y);

        if (target !== previousTarget) {
          if (previousTarget === currentTarget)
            option.onout(event);
          else if (target === currentTarget)
            option.onover(event);
        }
      });

      previousTarget = target;

      if (!isDragStarted)
        isDragStarted = true;
    };

    var end = function(event) {
      document.removeEventListener(MOVE, move);
      document.removeEventListener(END, end);

      var target = getTarget(event);
      var offset = getOffset(event, startOffset);

      currentOptions.forEach(function(option) {
        if (isDragStarted)
          option.ondragend(event, offset.x, offset.y);

        option.onend(event);

        if (target === currentTarget)
          option.ontap(event);
      });

      if (holdTimerId !== null) {
        clearTimeout(holdTimerId);
        holdTimerId = null;
      }

      lock = false;
    };

    return function(selector, option) {
      if (!isInitialized) {
        document.addEventListener(START, start);
        isInitialized = true;
      }

      var options = optionMap[selector];

      if (!options)
        options = optionMap[selector] = [];

      options.push(extend({
        onstart: noop,
        onhold: noop,
        ondragstart: noop,
        ondrag: noop,
        onout: noop,
        onover: noop,
        ondragend: noop,
        onend: noop,
        ontap: noop
      }, option));
    };
  })();

  function startDragEvent(event, option) {
    var startOffset;
    function moveListener(event) {
      cancelEvent(event);
      event = isTouchEnabled ? event.touches[0] : event;
      var dx = event.pageX - startOffset.left;
      var dy = event.pageY - startOffset.top;
      if (typeof option.drag === 'function')
        option.drag(dx, dy);
    }
    function endListener(event) {
      cancelEvent(event);
      if (isTouchEnabled && event.touches.length > 0)
        return;
      event = isTouchEnabled ? event.changedTouches[0] : event;
      document.removeEventListener(MOVE, moveListener, false);
      document.removeEventListener(END, endListener, false);
      var dx = event.pageX - startOffset.left;
      var dy = event.pageY - startOffset.top;
      if (typeof option.end === 'function')
        option.end(dx, dy);
    }
    cancelEvent(event);
    event = isTouchEnabled ? event.touches[0] : event;
    startOffset = {left: event.pageX, top: event.pageY};
    document.addEventListener(MOVE, moveListener, false);
    document.addEventListener(END, endListener, false);
  }

  function hasClass(el, className) {
    return el.classList.contains(className);
  }

  function addClass(el, className) {
    el.classList.add(className);
  }

  function removeClass(el, className) {
    el.classList.remove(className);
  }

  function indexOf(parentNode, childNode) {
    return Array.prototype.indexOf.call(parentNode.children, childNode);
  }

  function setCursor(value) {
    document.body.style.cursor = value;
  }

  function removeKeyboardFocus() {
    var activeElement = document.activeElement;
    if (activeElement && activeElement.blur)
      activeElement.blur();
  }

  function translate(el, x, y) {
    var value = 'translate(' + x + 'px, ' + y + 'px)';
    el.style.transform =  value;
    el.style.webkitTransform = value;
    el.style.MozTransform = value;
    el.style.msTransform = value;
    el.style.OTransform = value;
  }

  function requestAnimationFrame(callback) {
    return window.requestAnimationFrame(callback);
  }

  function supportsTouch() {
    return isTouchEnabled;
  }

  global.dom = {
    pointerEvent: pointerEvent,
    startDragEvent: startDragEvent,
    hasClass: hasClass,
    addClass: addClass,
    removeClass: removeClass,
    indexOf: indexOf,
    setCursor: setCursor,
    removeKeyboardFocus: removeKeyboardFocus,
    translate: translate,
    requestAnimationFrame: requestAnimationFrame,
    supportsTouch: supportsTouch
  };
})(this);<|endoftext|>


<commit_before>/*
 * node-rdkafka - Node.js wrapper for RdKafka C/C++ library
 *
 * Copyright (c) 2016 Blizzard Entertainment
 *
 * This software may be modified and distributed under the terms
 * of the MIT license.  See the LICENSE.txt file for details.
 */

'use strict';

module.exports = KafkaConsumerStream;

var Readable = require('stream').Readable;
var util = require('util');

util.inherits(KafkaConsumerStream, Readable);

/**
 * ReadableStream integrating with the Kafka Consumer.
 *
 * This class is used to read data off of Kafka in a streaming way. It is
 * useful if you'd like to have a way to pipe Kafka into other systems. You
 * should generally not make this class yourself, as it is not even exposed
 * as part of module.exports. Instead, you should KafkaConsumer.createReadStream.
 *
 * The stream implementation is slower than the continuous subscribe callback.
 * If you don't care so much about backpressure and would rather squeeze
 * out performance, use that method. Using the stream will ensure you read only
 * as fast as you write.
 *
 * The stream detects if Kafka is already connected. If it is, it will begin
 * reading. If it is not, it will connect and read when it is ready.
 *
 * This stream operates in objectMode. It streams {Consumer~Message}
 *
 * @param {Consumer} consumer - The Kafka Consumer object.
 * @param {object} options - Options to configure the stream.
 * @param {number} options.waitInterval - Number of ms to wait if Kafka reports
 * that it has timed out or that we are out of messages (right now).
 * @param {array} options.topics - Array of topics, or a function that parses
 * metadata into an array of topics
 * @constructor
 * @extends stream.Readable
 * @see Consumer~Message
 */
function KafkaConsumerStream(consumer, options) {
  if (!(this instanceof KafkaConsumerStream)) {
    return new KafkaConsumerStream(consumer, options);
  }

  if (options === undefined) {
    options = { waitInterval: 1000 };
  } else if (typeof options === 'number') {
    options = { waitInterval: options };
  } else if (options === null || typeof options !== 'object') {
    throw new TypeError('"options" argument must be a number or an object');
  }

  var topics = options.topics;

  if (typeof topics === 'function') {
    // Just ignore the rest of the checks here
  } else if (!Array.isArray(topics)) {
    if (typeof topics !== 'string' && !(topics instanceof RegExp)) {
      throw new TypeError('"topics" argument must be a string, regex, or an array');
    } else {
      topics = [topics];
    }
  }

  options = Object.create(options);

  var fetchSize = options.fetchSize || 1;

  // Run in object mode by default.
  if (options.objectMode === null || options.objectMode === undefined) {
    options.objectMode = true;

    // If they did not explicitly set high water mark, and we are running
    // in object mode, set it to the fetch size + 2
    if (!options.highWaterMark) {
      options.highWaterMark = fetchSize + 2;
    }
  }

  if (options.objectMode !== true) {
    this._read = this._read_buffer;
  } else {
    this._read = this._read_message;
  }

  Readable.call(this, options);

  this.consumer = consumer;
  this.topics = topics;
  this.autoClose = options.autoClose === undefined ? true : !!options.autoClose;
  this.waitInterval = options.waitInterval === undefined ? 1000 : options.waitInterval;
  this.fetchSize = fetchSize;
  this.connectOptions = options.connectOptions || {};

  // Hold the messages in here
  this.messages = [];

  var self = this;

  this.consumer
    .on('unsubscribed', function() {
      // Invalidate the stream when we unsubscribe
      self.push(null);
    });

  // Call connect. Handles potentially being connected already
  this.connect(this.connectOptions);

  this.once('end', function() {
    if (this.autoClose) {
      this.destroy();
    }
  });

}

/**
 * Internal stream read method. This method reads message objects.
 * @param {number} size - This parameter is ignored for our cases.
 * @private
 */
KafkaConsumerStream.prototype._read_message = function(size) {
  if (this.messages.length > 0) {
    return this.push(this.messages.shift());
  }

  if (!this.consumer.isConnected()) {
    this.consumer.once('ready', function() {
      // This is the way Node.js does it
      // https://github.com/nodejs/node/blob/master/lib/fs.js#L1733
      this._read(size);
    }.bind(this));
    return;
  }

  if (this.destroyed) {
    return;
  }

  var self = this;

  this.consumer.consume(size, onread);

  function onread(err, messages) {
    if (err) {
      // bad error
      if (self.autoClose) {
        self.destroy();
      }
      self.emit('error', err);
      return;
    }

    // If there are no messages it means we reached EOF or a timeout.
    // Do what we used to do

    if (messages.length < 1) {

      if (!self.waitInterval) {
        setImmediate(function() {
          self._read(size);
        });
      } else {
        setTimeout(function() {
          self._read(size);
        }, self.waitInterval * Math.random()).unref();
      }

      return;
    } else {
      for (var i = 0; i < messages.length; i++) {
        self.messages.push(messages[i]);
      }
      // Now that we have added them all the inner messages buffer,
      // we can just push the most recent one
      self.push(self.messages.shift());
    }

  }
};

/**
 * Internal stream read method. This method reads message buffers.
 * @param {number} size - This parameter is ignored for our cases.
 * @private
 */
KafkaConsumerStream.prototype._read_buffer = function(size) {
  if (this.messages.length > 0) {
    return this.push(this.messages.shift());
  }

  if (!this.consumer.isConnected()) {
    this.consumer.once('ready', function() {
      // This is the way Node.js does it
      // https://github.com/nodejs/node/blob/master/lib/fs.js#L1733
      this._read(size);
    }.bind(this));
    return;
  }

  if (this.destroyed) {
    return;
  }

  var self = this;

  this.consumer.consume(this.fetchSize, onread);

  function onread(err, messages) {
    if (err) {
      // bad error
      if (self.autoClose) {
        self.destroy();
      }
      self.emit('error', err);
      return;
    }

    // If there are no messages it means we reached EOF or a timeout.
    // Do what we used to do

    if (messages.length < 1) {

      if (!self.waitInterval) {
        setImmediate(function() {
          self._read(size);
        });
      } else {
        setTimeout(function() {
          self._read(size);
        }, self.waitInterval * Math.random()).unref();
      }

      return;
    } else {
      for (var i = 0; i < messages.length; i++) {
        self.push(messages[i].value);
      }
      // Now that we have added them all the inner messages buffer,
      // we can just push the most recent one
      self.push(self.messages.shift());
    }

  }
};

KafkaConsumerStream.prototype.connect = function(options) {
  var self = this;

  function connectCallback(err, metadata) {
    if (err) {
      self.emit('error', err);
      self.destroy();
      return;
    }

    try {
      // Subscribe to the topics as well so we will be ready
      // If this throws the stream is invalid

      // This is the magic part. If topics is a function, before we subscribe,
      // pass the metadata in
      if (typeof self.topics === 'function') {
        var topics = self.topics(metadata);
        self.consumer.subscribe(topics);
      } else {
        self.consumer.subscribe(self.topics);
      }
    } catch (e) {
      self.emit('error', e);
      self.destroy();
      return;
    }

    // start the flow of data
    self.read();
  }

  if (!this.consumer.isConnected()) {
    self.consumer.connect(options, connectCallback);
  } else {
    // Immediately call the connect callback
    setImmediate(function() {
      connectCallback(null, self.consumer._metadata);
    });
  }

};

KafkaConsumerStream.prototype.destroy = function() {
  if (this.destroyed) {
    return;
  }
  this.destroyed = true;
  this.close();
};

KafkaConsumerStream.prototype.close = function(cb) {
  var self = this;
  if (cb) {
    this.once('close', cb);
  }

  if (!self.consumer._isConnecting && !self.consumer._isConnected) {
    // If we aren't even connected just exit. We are done.
    close();
    return;
  }

  if (self.consumer._isConnecting) {
    self.consumer.once('ready', function() {
      // Don't pass the CB because it has already been passed.
      self.close();
    });
    return;
  }

  if (self.consumer._isConnected) {
    self.consumer.unsubscribe();
    self.consumer.disconnect(function() {
      self.consumer = null;
      close();
    });
  }

  function close() {
    self.emit('close');
  }
};
<commit_msg>Non object mode stream should push to messages buffer<commit_after>/*
 * node-rdkafka - Node.js wrapper for RdKafka C/C++ library
 *
 * Copyright (c) 2016 Blizzard Entertainment
 *
 * This software may be modified and distributed under the terms
 * of the MIT license.  See the LICENSE.txt file for details.
 */

'use strict';

module.exports = KafkaConsumerStream;

var Readable = require('stream').Readable;
var util = require('util');

util.inherits(KafkaConsumerStream, Readable);

/**
 * ReadableStream integrating with the Kafka Consumer.
 *
 * This class is used to read data off of Kafka in a streaming way. It is
 * useful if you'd like to have a way to pipe Kafka into other systems. You
 * should generally not make this class yourself, as it is not even exposed
 * as part of module.exports. Instead, you should KafkaConsumer.createReadStream.
 *
 * The stream implementation is slower than the continuous subscribe callback.
 * If you don't care so much about backpressure and would rather squeeze
 * out performance, use that method. Using the stream will ensure you read only
 * as fast as you write.
 *
 * The stream detects if Kafka is already connected. If it is, it will begin
 * reading. If it is not, it will connect and read when it is ready.
 *
 * This stream operates in objectMode. It streams {Consumer~Message}
 *
 * @param {Consumer} consumer - The Kafka Consumer object.
 * @param {object} options - Options to configure the stream.
 * @param {number} options.waitInterval - Number of ms to wait if Kafka reports
 * that it has timed out or that we are out of messages (right now).
 * @param {array} options.topics - Array of topics, or a function that parses
 * metadata into an array of topics
 * @constructor
 * @extends stream.Readable
 * @see Consumer~Message
 */
function KafkaConsumerStream(consumer, options) {
  if (!(this instanceof KafkaConsumerStream)) {
    return new KafkaConsumerStream(consumer, options);
  }

  if (options === undefined) {
    options = { waitInterval: 1000 };
  } else if (typeof options === 'number') {
    options = { waitInterval: options };
  } else if (options === null || typeof options !== 'object') {
    throw new TypeError('"options" argument must be a number or an object');
  }

  var topics = options.topics;

  if (typeof topics === 'function') {
    // Just ignore the rest of the checks here
  } else if (!Array.isArray(topics)) {
    if (typeof topics !== 'string' && !(topics instanceof RegExp)) {
      throw new TypeError('"topics" argument must be a string, regex, or an array');
    } else {
      topics = [topics];
    }
  }

  options = Object.create(options);

  var fetchSize = options.fetchSize || 1;

  // Run in object mode by default.
  if (options.objectMode === null || options.objectMode === undefined) {
    options.objectMode = true;

    // If they did not explicitly set high water mark, and we are running
    // in object mode, set it to the fetch size + 2
    if (!options.highWaterMark) {
      options.highWaterMark = fetchSize + 2;
    }
  }

  if (options.objectMode !== true) {
    this._read = this._read_buffer;
  } else {
    this._read = this._read_message;
  }

  Readable.call(this, options);

  this.consumer = consumer;
  this.topics = topics;
  this.autoClose = options.autoClose === undefined ? true : !!options.autoClose;
  this.waitInterval = options.waitInterval === undefined ? 1000 : options.waitInterval;
  this.fetchSize = fetchSize;
  this.connectOptions = options.connectOptions || {};

  // Hold the messages in here
  this.messages = [];

  var self = this;

  this.consumer
    .on('unsubscribed', function() {
      // Invalidate the stream when we unsubscribe
      self.push(null);
    });

  // Call connect. Handles potentially being connected already
  this.connect(this.connectOptions);

  this.once('end', function() {
    if (this.autoClose) {
      this.destroy();
    }
  });

}

/**
 * Internal stream read method. This method reads message objects.
 * @param {number} size - This parameter is ignored for our cases.
 * @private
 */
KafkaConsumerStream.prototype._read_message = function(size) {
  if (this.messages.length > 0) {
    return this.push(this.messages.shift());
  }

  if (!this.consumer.isConnected()) {
    this.consumer.once('ready', function() {
      // This is the way Node.js does it
      // https://github.com/nodejs/node/blob/master/lib/fs.js#L1733
      this._read(size);
    }.bind(this));
    return;
  }

  if (this.destroyed) {
    return;
  }

  var self = this;

  this.consumer.consume(size, onread);

  function onread(err, messages) {
    if (err) {
      // bad error
      if (self.autoClose) {
        self.destroy();
      }
      self.emit('error', err);
      return;
    }

    // If there are no messages it means we reached EOF or a timeout.
    // Do what we used to do

    if (messages.length < 1) {

      if (!self.waitInterval) {
        setImmediate(function() {
          self._read(size);
        });
      } else {
        setTimeout(function() {
          self._read(size);
        }, self.waitInterval * Math.random()).unref();
      }

      return;
    } else {
      for (var i = 0; i < messages.length; i++) {
        self.messages.push(messages[i]);
      }
      // Now that we have added them all the inner messages buffer,
      // we can just push the most recent one
      self.push(self.messages.shift());
    }

  }
};

/**
 * Internal stream read method. This method reads message buffers.
 * @param {number} size - This parameter is ignored for our cases.
 * @private
 */
KafkaConsumerStream.prototype._read_buffer = function(size) {
  if (this.messages.length > 0) {
    return this.push(this.messages.shift());
  }

  if (!this.consumer.isConnected()) {
    this.consumer.once('ready', function() {
      // This is the way Node.js does it
      // https://github.com/nodejs/node/blob/master/lib/fs.js#L1733
      this._read(size);
    }.bind(this));
    return;
  }

  if (this.destroyed) {
    return;
  }

  var self = this;

  this.consumer.consume(this.fetchSize, onread);

  function onread(err, messages) {
    if (err) {
      // bad error
      if (self.autoClose) {
        self.destroy();
      }
      self.emit('error', err);
      return;
    }

    // If there are no messages it means we reached EOF or a timeout.
    // Do what we used to do

    if (messages.length < 1) {

      if (!self.waitInterval) {
        setImmediate(function() {
          self._read(size);
        });
      } else {
        setTimeout(function() {
          self._read(size);
        }, self.waitInterval * Math.random()).unref();
      }

      return;
    } else {
      for (var i = 0; i < messages.length; i++) {
        self.messages.push(messages[i].value);
      }
      // Now that we have added them all the inner messages buffer,
      // we can just push the most recent one
      self.push(self.messages.shift());
    }

  }
};

KafkaConsumerStream.prototype.connect = function(options) {
  var self = this;

  function connectCallback(err, metadata) {
    if (err) {
      self.emit('error', err);
      self.destroy();
      return;
    }

    try {
      // Subscribe to the topics as well so we will be ready
      // If this throws the stream is invalid

      // This is the magic part. If topics is a function, before we subscribe,
      // pass the metadata in
      if (typeof self.topics === 'function') {
        var topics = self.topics(metadata);
        self.consumer.subscribe(topics);
      } else {
        self.consumer.subscribe(self.topics);
      }
    } catch (e) {
      self.emit('error', e);
      self.destroy();
      return;
    }

    // start the flow of data
    self.read();
  }

  if (!this.consumer.isConnected()) {
    self.consumer.connect(options, connectCallback);
  } else {
    // Immediately call the connect callback
    setImmediate(function() {
      connectCallback(null, self.consumer._metadata);
    });
  }

};

KafkaConsumerStream.prototype.destroy = function() {
  if (this.destroyed) {
    return;
  }
  this.destroyed = true;
  this.close();
};

KafkaConsumerStream.prototype.close = function(cb) {
  var self = this;
  if (cb) {
    this.once('close', cb);
  }

  if (!self.consumer._isConnecting && !self.consumer._isConnected) {
    // If we aren't even connected just exit. We are done.
    close();
    return;
  }

  if (self.consumer._isConnecting) {
    self.consumer.once('ready', function() {
      // Don't pass the CB because it has already been passed.
      self.close();
    });
    return;
  }

  if (self.consumer._isConnected) {
    self.consumer.unsubscribe();
    self.consumer.disconnect(function() {
      self.consumer = null;
      close();
    });
  }

  function close() {
    self.emit('close');
  }
};
<|endoftext|>


<commit_before>/**
 * @license The MIT License (MIT)
 *
 * Copyright (c) 2014 Felipe O. Carvalho
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
define(['JSXTransformer', 'text'], function (JSXTransformer, text) {

  'use strict';

  var buildMap = {};

  var jsx = {
    version: '0.2.0',

    load: function (name, req, onLoadNative, config) {
      var fileExtension = config.jsx && config.jsx.fileExtension || '.js';

      var onLoad = function(content) {
        try {
          if (-1 === content.indexOf('@jsx React.DOM')) {
            content = "/** @jsx React.DOM */\n" + content;
          }
          content = JSXTransformer.transform(content).code;
        } catch (err) {
          onLoadNative.error(err);
        }

        if (config.isBuild) {
          buildMap[name] = content;
        }

        onLoadNative.fromText(content);
      };

      text.load(name + fileExtension, req, onLoad, config);
    },

    write: function (pluginName, moduleName, write) {
      if (buildMap.hasOwnProperty(moduleName)) {
        var content = buildMap[moduleName];
        write.asModule(moduleName, content);
      }
    }
  };

  return jsx;
});
<commit_msg>enable debugging by adding sourceURL mapping<commit_after>/**
 * @license The MIT License (MIT)
 *
 * Copyright (c) 2014 Felipe O. Carvalho
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
define(['JSXTransformer', 'text'], function (JSXTransformer, text) {

  'use strict';

  var buildMap = {};

  var jsx = {
    version: '0.2.0',

    load: function (name, req, onLoadNative, config) {
      var fileExtension = config.jsx && config.jsx.fileExtension || '.js';

      var onLoad = function(content) {
        try {
          if (-1 === content.indexOf('@jsx React.DOM')) {
            content = "/** @jsx React.DOM */\n" + content;
          }
          content = JSXTransformer.transform(content).code;
        } catch (err) {
          onLoadNative.error(err);
        }

        if (config.isBuild) {
          buildMap[name] = content;
        } else {
          content += "\n//# sourceURL=" + location.protocol + "//" + location.hostname +
            config.baseUrl + name + ".jsx";
        }

        onLoadNative.fromText(content);
      };

      text.load(name + fileExtension, req, onLoad, config);
    },

    write: function (pluginName, moduleName, write) {
      if (buildMap.hasOwnProperty(moduleName)) {
        var content = buildMap[moduleName];
        write.asModule(moduleName, content);
      }
    }
  };

  return jsx;
});
<|endoftext|>


<commit_before>/**
 * Created by jayantbhawal on 14/12/15.
 */
var Dictionary;

(function () {
    var storage = chrome.storage.local;
    storage.get("dict", function (dict) {
        dict = dict.dict;
        if (dict) {
            Dictionary = dict;
            console.log("Dictionary loaded with " + Dictionary.length + " words.");
        }
        else {
            $.get(chrome.extension.getURL('dict.txt'), function (words) {
                Dictionary = words.split("\n");
                console.log("Dictionary loaded with " + Dictionary.length + " words.");
                storage.set({"dict": Dictionary});
            });
        }
    });
})();

function isLetter(str) {
    return str.length === 1 && str.match(/[a-z]/i);
}

function StartsWith(string, prefix) {
    string = string.toLowerCase();
    prefix = prefix.toLowerCase();
    return string.slice(0, prefix.length) == prefix;
}

function AddText(el, newText) {
    var start = el.selectionStart;
    var end = el.selectionEnd;
    var text = el.value;
    var before = text.substring(0, start);
    var after = text.substring(end, text.length);
    el.value = (before + newText + after);
    el.selectionStart = el.selectionEnd = start + newText.length;
}

function RemoveSelectedText(){
    var el  = document.activeElement;
    var text = el.value;

    text = text.slice(0, el.selectionStart) + text.slice(el.selectionEnd);
    el.value = text;
    //console.log(text);
}

function GetCaretPosition(ctrl) {
    var CaretPos = 0;   // IE Support
    if (document.selection) {
        ctrl.focus();
        var Sel = document.selection.createRange();
        Sel.moveStart('character', -ctrl.value.length);
        CaretPos = Sel.text.length;
    }
    // Firefox support
    else if (ctrl.selectionStart || ctrl.selectionStart == '0')
        CaretPos = ctrl.selectionStart;
    return (CaretPos);
}

function ReturnWord(text, caretPos) {
    var index = text.indexOf(caretPos);
    var preText = text.substring(0, caretPos);
    if (preText.indexOf(" ") > 0) {
        var words = preText.split(" ");
        return words[words.length - 1]; //return last word
    }
    else {
        return preText;
    }
}

function GetCurrentWord() {
    var caretPos = GetCaretPosition(document.activeElement);
    var word = ReturnWord(document.activeElement.value, caretPos);
    if (word) {
        if (isLetter(word[word.length - 1])) {
            word = word.replace(window.getSelection().toString(),"").substr(word.search(/[\S]*$/)).replace(/[^\w\-.@]/g, "");
            return word;
        }
    }
}

function AddCurrentWordToDictionary() {
    var str = GetCurrentWord();

    var storage = chrome.storage.local;
    if ($.inArray(str, Dictionary) == -1 && $.inArray(str.toLowerCase(), Dictionary) == -1) {
        str = str.toLowerCase();
        Dictionary = Dictionary.concat(str);
        storage.set({"dict": Dictionary});
        console.log(str + " added.");
    }
}

function BestMatch(str, callback) {
    str = str.toLowerCase();
    var old_time = (new Date()).getTime();
    var new_time, time_passed;
    var val = "";
    var distance = 9999;
    var word = "";
    var iterations = 0;
    var length = Dictionary.length;
    for (var i = 0; i < length; i++) {
        val = Dictionary[i].toLowerCase();
        iterations++;
        if (val.length > str.length) {
            if (StartsWith(val, str)) {
                var ld = Levenshtein(val, str);
                if (ld < distance) {
                    distance = ld;
                    word = val;
                    if (distance <= 1) {
                        new_time = (new Date()).getTime();
                        time_passed = new_time - old_time;
                        callback(word, iterations, time_passed);
                        break;
                    }
                }
            }
        }
    }
    new_time = (new Date()).getTime();
    time_passed = new_time - old_time;
    callback(word, iterations, time_passed);
}

function BestReplacementForCurrentWord(str,callback) {
    var str = str.toLowerCase();
    var old_time = (new Date()).getTime();
    var new_time, time_passed;
    var val = "";
    var distance = 9999;
    var replacement = "";
    var iterations = 0;
    var length = Dictionary.length;
    for (var i = 0; i < length; i++) {
        val = Dictionary[i].toLowerCase();
        iterations++;
        var ld = Levenshtein(val, str);
        if (ld < distance) {
            distance = ld;
            replacement = val;
            if (distance == 0) {
                new_time = (new Date()).getTime();
                time_passed = new_time - old_time;
                callback("", iterations, time_passed);
                break;
            }
        }
        if(ld == distance){
            if(val.length == str.length){
                replacement = val;
            }
        }
    }
    console.log(distance);
    new_time = (new Date()).getTime();
    time_passed = new_time - old_time;
    if (distance == 1 || distance == 2) {
        callback(replacement, iterations, time_passed);
    }
    else {
        AddCurrentWordToDictionary();
        callback("", iterations, time_passed);
    }
}<commit_msg>Switched to indexOf from jQuery.inArray for possible performance improvements.<commit_after>/**
 * Created by jayantbhawal on 14/12/15.
 */
var Dictionary;

(function () {
    var storage = chrome.storage.local;
    storage.get("dict", function (dict) {
        dict = dict.dict;
        if (dict) {
            Dictionary = dict;
            console.log("Dictionary loaded with " + Dictionary.length + " words.");
        }
        else {
            $.get(chrome.extension.getURL('dict.txt'), function (words) {
                Dictionary = words.split("\n");
                console.log("Dictionary loaded with " + Dictionary.length + " words.");
                storage.set({"dict": Dictionary});
            });
        }
    });
})();

function isLetter(str) {
    return str.length === 1 && str.match(/[a-z]/i);
}

function StartsWith(string, prefix) {
    string = string.toLowerCase();
    prefix = prefix.toLowerCase();
    return string.slice(0, prefix.length) == prefix;
}

function AddText(el, newText) {
    var start = el.selectionStart;
    var end = el.selectionEnd;
    var text = el.value;
    var before = text.substring(0, start);
    var after = text.substring(end, text.length);
    el.value = (before + newText + after);
    el.selectionStart = el.selectionEnd = start + newText.length;
}

function RemoveSelectedText(){
    var el  = document.activeElement;
    var text = el.value;

    text = text.slice(0, el.selectionStart) + text.slice(el.selectionEnd);
    el.value = text;
    //console.log(text);
}

function GetCaretPosition(ctrl) {
    var CaretPos = 0;   // IE Support
    if (document.selection) {
        ctrl.focus();
        var Sel = document.selection.createRange();
        Sel.moveStart('character', -ctrl.value.length);
        CaretPos = Sel.text.length;
    }
    // Firefox support
    else if (ctrl.selectionStart || ctrl.selectionStart == '0')
        CaretPos = ctrl.selectionStart;
    return (CaretPos);
}

function ReturnWord(text, caretPos) {
    var index = text.indexOf(caretPos);
    var preText = text.substring(0, caretPos);
    if (preText.indexOf(" ") > 0) {
        var words = preText.split(" ");
        return words[words.length - 1]; //return last word
    }
    else {
        return preText;
    }
}

function GetCurrentWord() {
    var caretPos = GetCaretPosition(document.activeElement);
    var word = ReturnWord(document.activeElement.value, caretPos);
    if (word) {
        if (isLetter(word[word.length - 1])) {
            word = word.replace(window.getSelection().toString(),"").substr(word.search(/[\S]*$/)).replace(/[^\w\-.@]/g, "");
            return word;
        }
    }
}

function AddCurrentWordToDictionary() {
    var str = GetCurrentWord();

    var storage = chrome.storage.local;
    if (Dictionary.indexOf(str) == -1 && Dictionary.indexOf(str.toLowerCase()) == -1) {
        str = str.toLowerCase();
        Dictionary = Dictionary.concat(str);
        storage.set({"dict": Dictionary});
        console.log(str + " added.");
    }
}

function BestMatch(str, callback) {
    str = str.toLowerCase();
    var old_time = (new Date()).getTime();
    var new_time, time_passed;
    var val = "";
    var distance = 9999;
    var word = "";
    var iterations = 0;
    var length = Dictionary.length;
    for (var i = 0; i < length; i++) {
        val = Dictionary[i].toLowerCase();
        iterations++;
        if (val.length > str.length) {
            if (StartsWith(val, str)) {
                var ld = Levenshtein(val, str);
                if (ld < distance) {
                    distance = ld;
                    word = val;
                    if (distance <= 1) {
                        new_time = (new Date()).getTime();
                        time_passed = new_time - old_time;
                        callback(word, iterations, time_passed);
                        break;
                    }
                }
            }
        }
    }
    new_time = (new Date()).getTime();
    time_passed = new_time - old_time;
    callback(word, iterations, time_passed);
}

function BestReplacementForCurrentWord(str,callback) {
    var str = str.toLowerCase();
    var old_time = (new Date()).getTime();
    var new_time, time_passed;
    var val = "";
    var distance = 9999;
    var replacement = "";
    var iterations = 0;
    var length = Dictionary.length;
    for (var i = 0; i < length; i++) {
        val = Dictionary[i].toLowerCase();
        iterations++;
        var ld = Levenshtein(val, str);
        if (ld < distance) {
            distance = ld;
            replacement = val;
            if (distance == 0) {
                new_time = (new Date()).getTime();
                time_passed = new_time - old_time;
                callback("", iterations, time_passed);
                break;
            }
        }
        if(ld == distance){
            if(val.length == str.length){
                replacement = val;
            }
        }
    }
    console.log(distance);
    new_time = (new Date()).getTime();
    time_passed = new_time - old_time;
    if (distance == 1 || distance == 2) {
        callback(replacement, iterations, time_passed);
    }
    else {
        AddCurrentWordToDictionary();
        callback("", iterations, time_passed);
    }
}<|endoftext|>