text: /**
  *  Ajax Autocomplete for jQuery, version 1.2.12
  *  (c) 2014 Tomas Kirda
  *
  *  Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
  *  For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
  */
  
  /*jslint  browser: true, white: true, plusplus: true */
  /*global define, window, document, jQuery, exports */
  
  // Expose plugin as an AMD module if AMD loader is present:
  (function (factory) {
      'use strict';
      if (typeof define === 'function' && define.amd) {
          // AMD. Register as an anonymous module.
          define(['jquery'], factory);
      } else if (typeof exports === 'object' && typeof require === 'function') {
          // Browserify
          factory(require('jquery'));
      } else {
          // Browser globals
          factory(jQuery);
      }
  }(function ($) {
      'use strict';
  
      var
          utils = (function () {
              return {
                  escapeRegExChars: function (value) {
                      return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
                  },
                  createNode: function (containerClass) {
                      var div = document.createElement('div');
                      div.className = containerClass;
                      div.style.position = 'absolute';
                      div.style.display = 'none';
                      return div;
                  }
              };
          }()),
  
          keys = {
              ESC: 27,
              TAB: 9,
              RETURN: 13,
              LEFT: 37,
              UP: 38,
              RIGHT: 39,
              DOWN: 40
          };
  
      function Autocomplete(el, options) {
          var noop = function () { },
              that = this,
              defaults = {
                  ajaxSettings: {},
                  autoSelectFirst: false,
                  appendTo: document.body,
                  serviceUrl: null,
                  lookup: null,
                  onSelect: null,
                  width: 'auto',
                  minChars: 1,
                  maxHeight: 300,
                  deferRequestBy: 0,
                  params: {},
                  formatResult: Autocomplete.formatResult,
                  delimiter: null,
                  zIndex: 9999,
                  type: 'GET',
                  noCache: false,
                  onSearchStart: noop,
                  onSearchComplete: noop,
                  onSearchError: noop,
                  containerClass: 'autocomplete-suggestions',
                  tabDisabled: false,
                  dataType: 'text',
                  currentRequest: null,
                  triggerSelectOnValidInput: true,
                  preventBadQueries: true,
                  lookupFilter: function (suggestion, originalQuery, queryLowerCase) {
                      return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1;
                  },
                  paramName: 'query',
                  transformResult: function (response) {
                      return typeof response === 'string' ? $.parseJSON(response) : response;
                  },
                  showNoSuggestionNotice: false,
                  noSuggestionNotice: 'No results',
                  orientation: 'bottom',
                  forceFixPosition: false
              };
  
          // Shared variables:
          that.element = el;
          that.el = $(el);
          that.suggestions = [];
          that.badQueries = [];
          that.selectedIndex = -1;
          that.currentValue = that.element.value;
          that.intervalId = 0;
          that.cachedResponse = {};
          that.onChangeInterval = null;
          that.onChange = null;
          that.isLocal = false;
          that.suggestionsContainer = null;
          that.noSuggestionsContainer = null;
          that.options = $.extend({}, defaults, options);
          that.classes = {
              selected: 'autocomplete-selected',
              suggestion: 'autocomplete-suggestion'
          };
          that.hint = null;
          that.hintValue = '';
          that.selection = null;
  
          // Initialize and set options:
          that.initialize();
          that.setOptions(options);
      }
  
      Autocomplete.utils = utils;
  
      $.Autocomplete = Autocomplete;
  
      Autocomplete.formatResult = function (suggestion, currentValue) {
          var pattern = '(' + utils.escapeRegExChars(currentValue) + ')';
  
          return suggestion.value.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
      };
  
      Autocomplete.prototype = {
  
          killerFn: null,
  
          initialize: function () {
              var that = this,
                  suggestionSelector = '.' + that.classes.suggestion,
                  selected = that.classes.selected,
                  options = that.options,
                  container;
  
              // Remove autocomplete attribute to prevent native suggestions:
              that.element.setAttribute('autocomplete', 'off');
  
              that.killerFn = function (e) {
                  if ($(e.target).closest('.' + that.options.containerClass).length === 0) {
                      that.killSuggestions();
                      that.disableKillerFn();
                  }
              };
  
              // html() deals with many types: htmlString or Element or Array or jQuery
              that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>')
                                            .html(this.options.noSuggestionNotice).get(0);
  
              that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass);
  
              container = $(that.suggestionsContainer);
  
              container.appendTo(options.appendTo);
  
              // Only set width if it was provided:
              if (options.width !== 'auto') {
                  container.width(options.width);
              }
  
              // Listen for mouse over event on suggestions list:
              container.on('mouseover.autocomplete', suggestionSelector, function () {
                  that.activate($(this).data('index'));
              });
  
              // Deselect active element when mouse leaves suggestions container:
              container.on('mouseout.autocomplete', function () {
                  that.selectedIndex = -1;
                  container.children('.' + selected).removeClass(selected);
              });
  
              // Listen for click event on suggestions list:
              container.on('click.autocomplete', suggestionSelector, function () {
                  that.select($(this).data('index'));
              });
  
              that.fixPositionCapture = function () {
                  if (that.visible) {
                      that.fixPosition();
                  }
              };
  
              $(window).on('resize.autocomplete', that.fixPositionCapture);
  
              that.el.on('keydown.autocomplete', function (e) { that.onKeyPress(e); });
              that.el.on('keyup.autocomplete', function (e) { that.onKeyUp(e); });
              that.el.on('blur.autocomplete', function () { that.onBlur(); });
              that.el.on('focus.autocomplete', function () { that.onFocus(); });
              that.el.on('change.autocomplete', function (e) { that.onKeyUp(e); });
          },
  
          onFocus: function () {
              var that = this;
              that.fixPosition();
              if (that.options.minChars <= that.el.val().length) {
                  that.onValueChange();
              }
          },
  
          onBlur: function () {
              this.enableKillerFn();
          },
  
          setOptions: function (suppliedOptions) {
              var that = this,
                  options = that.options;
  
              $.extend(options, suppliedOptions);
  
              that.isLocal = $.isArray(options.lookup);
  
              if (that.isLocal) {
                  options.lookup = that.verifySuggestionsFormat(options.lookup);
              }
  
              options.orientation = that.validateOrientation(options.orientation, 'bottom');
  
              // Adjust height, width and z-index:
              $(that.suggestionsContainer).css({
                  'max-height': options.maxHeight + 'px',
                  'width': options.width + 'px',
                  'z-index': options.zIndex
              });
          },
  
  
          clearCache: function () {
              this.cachedResponse = {};
              this.badQueries = [];
          },
  
          clear: function () {
              this.clearCache();
              this.currentValue = '';
              this.suggestions = [];
          },
  
          disable: function () {
              var that = this;
              that.disabled = true;
              if (that.currentRequest) {
                  that.currentRequest.abort();
              }
          },
  
          enable: function () {
              this.disabled = false;
          },
  
          fixPosition: function () {
              // Use only when container has already its content
  
              var that = this,
                  $container = $(that.suggestionsContainer),
                  containerParent = $container.parent().get(0);
              // Fix position automatically when appended to body.
              // In other cases force parameter must be given.
              if (containerParent !== document.body && !that.options.forceFixPosition)
                  return;
  
              // Choose orientation
              var orientation = that.options.orientation,
                  containerHeight = $container.outerHeight(),
                  height = that.el.outerHeight(),
                  offset = that.el.offset(),
                  styles = { 'top': offset.top, 'left': offset.left };
  
              if (orientation == 'auto') {
                  var viewPortHeight = $(window).height(),
                      scrollTop = $(window).scrollTop(),
                      topOverflow = -scrollTop + offset.top - containerHeight,
                      bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight);
  
                  orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow)
                                  ? 'top'
                                  : 'bottom';
              }
  
              if (orientation === 'top') {
                  styles.top += -containerHeight;
              } else {
                  styles.top += height;
              }
  
              // If container is not positioned to body,
              // correct its position using offset parent offset
              if(containerParent !== document.body) {
                  var opacity = $container.css('opacity'),
                      parentOffsetDiff;
  
                      if (!that.visible){
                          $container.css('opacity', 0).show();
                      }
  
                  parentOffsetDiff = $container.offsetParent().offset();
                  styles.top -= parentOffsetDiff.top;
                  styles.left -= parentOffsetDiff.left;
  
                  if (!that.visible){
                      $container.css('opacity', opacity).hide();
                  }
              }
  
              // -2px to account for suggestions border.
              if (that.options.width === 'auto') {
                  styles.width = (that.el.outerWidth() - 2) + 'px';
              }
  
              $container.css(styles);
          },
  
          enableKillerFn: function () {
              var that = this;
              $(document).on('click.autocomplete', that.killerFn);
          },
  
          disableKillerFn: function () {
              var that = this;
              $(document).off('click.autocomplete', that.killerFn);
          },
  
          killSuggestions: function () {
              var that = this;
              that.stopKillSuggestions();
              that.intervalId = window.setInterval(function () {
                  that.hide();
                  that.stopKillSuggestions();
              }, 50);
          },
  
          stopKillSuggestions: function () {
              window.clearInterval(this.intervalId);
          },
  
          isCursorAtEnd: function () {
              var that = this,
                  valLength = that.el.val().length,
                  selectionStart = that.element.selectionStart,
                  range;
  
              if (typeof selectionStart === 'number') {
                  return selectionStart === valLength;
              }
              if (document.selection) {
                  range = document.selection.createRange();
                  range.moveStart('character', -valLength);
                  return valLength === range.text.length;
              }
              return true;
          },
  
          onKeyPress: function (e) {
              var that = this;
  
              // If suggestions are hidden and user presses arrow down, display suggestions:
              if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) {
                  that.suggest();
                  return;
              }
  
              if (that.disabled || !that.visible) {
                  return;
              }
  
              switch (e.which) {
                  case keys.ESC:
                      that.el.val(that.currentValue);
                      that.hide();
                      break;
                  case keys.RIGHT:
                      if (that.hint && that.options.onHint && that.isCursorAtEnd()) {
                          that.selectHint();
                          break;
                      }
                      return;
                  case keys.TAB:
                      if (that.hint && that.options.onHint) {
                          that.selectHint();
                          return;
                      }
                      // Fall through to RETURN
                  case keys.RETURN:
                      if (that.selectedIndex === -1) {
                          that.hide();
                          return;
                      }
                      that.select(that.selectedIndex);
                      if (e.which === keys.TAB && that.options.tabDisabled === false) {
                          return;
                      }
                      break;
                  case keys.UP:
                      that.moveUp();
                      break;
                  case keys.DOWN:
                      that.moveDown();
                      break;
                  default:
                      return;
              }
  
              // Cancel event if function did not return:
              e.stopImmediatePropagation();
              e.preventDefault();
          },
  
          onKeyUp: function (e) {
              var that = this;
  
              if (that.disabled) {
                  return;
              }
  
              switch (e.which) {
                  case keys.UP:
                  case keys.DOWN:
                      return;
              }
  
              clearInterval(that.onChangeInterval);
  
              if (that.currentValue !== that.el.val()) {
                  that.findBestHint();
                  if (that.options.deferRequestBy > 0) {
                      // Defer lookup in case when value changes very quickly:
                      that.onChangeInterval = setInterval(function () {
                          that.onValueChange();
                      }, that.options.deferRequestBy);
                  } else {
                      that.onValueChange();
                  }
              }
          },
  
          onValueChange: function () {
              var that = this,
                  options = that.options,
                  value = that.el.val(),
                  query = that.getQuery(value),
                  index;
  
              if (that.selection && that.currentValue !== query) {
                  that.selection = null;
                  (options.onInvalidateSelection || $.noop).call(that.element);
              }
  
              clearInterval(that.onChangeInterval);
              that.currentValue = value;
              that.selectedIndex = -1;
  
              // Check existing suggestion for the match before proceeding:
              if (options.triggerSelectOnValidInput) {
                  index = that.findSuggestionIndex(query);
                  if (index !== -1) {
                      that.select(index);
                      return;
                  }
              }
  
              if (query.length < options.minChars) {
                  that.hide();
              } else {
                  that.getSuggestions(query);
              }
          },
  
          findSuggestionIndex: function (query) {
              var that = this,
                  index = -1,
                  queryLowerCase = query.toLowerCase();
  
              $.each(that.suggestions, function (i, suggestion) {
                  if (suggestion.value.toLowerCase() === queryLowerCase) {
                      index = i;
                      return false;
                  }
              });
  
              return index;
          },
  
          getQuery: function (value) {
              var delimiter = this.options.delimiter,
                  parts;
  
              if (!delimiter) {
                  return value;
              }
              parts = value.split(delimiter);
              return $.trim(parts[parts.length - 1]);
          },
  
          getSuggestionsLocal: function (query) {
              var that = this,
                  options = that.options,
                  queryLowerCase = query.toLowerCase(),
                  filter = options.lookupFilter,
                  limit = parseInt(options.lookupLimit, 10),
                  data;
  
              data = {
                  suggestions: $.grep(options.lookup, function (suggestion) {
                      return filter(suggestion, query, queryLowerCase);
                  })
              };
  
              if (limit && data.suggestions.length > limit) {
                  data.suggestions = data.suggestions.slice(0, limit);
              }
  
              return data;
          },
  
          getSuggestions: function (q) {
              var response,
                  that = this,
                  options = that.options,
                  serviceUrl = options.serviceUrl,
                  params,
                  cacheKey,
                  ajaxSettings;
  
              options.params[options.paramName] = q;
              params = options.ignoreParams ? null : options.params;
  
              if (that.isLocal) {
                  response = that.getSuggestionsLocal(q);
              } else {
                  if ($.isFunction(serviceUrl)) {
                      serviceUrl = serviceUrl.call(that.element, q);
                  }
                  cacheKey = serviceUrl + '?' + $.param(params || {});
                  response = that.cachedResponse[cacheKey];
              }
  
              if (response && $.isArray(response.suggestions)) {
                  that.suggestions = response.suggestions;
                  that.suggest();
              } else if (!that.isBadQuery(q)) {
                  if (options.onSearchStart.call(that.element, options.params) === false) {
                      return;
                  }
                  if (that.currentRequest) {
                      that.currentRequest.abort();
                  }
  
                  ajaxSettings = {
                      url: serviceUrl,
                      data: params,
                      type: options.type,
                      dataType: options.dataType
                  };
  
                  $.extend(ajaxSettings, options.ajaxSettings);
  
                  that.currentRequest = $.ajax(ajaxSettings).done(function (data) {
                      var result;
                      that.currentRequest = null;
                      result = options.transformResult(data);
                      that.processResponse(result, q, cacheKey);
                      options.onSearchComplete.call(that.element, q, result.suggestions);
                  }).fail(function (jqXHR, textStatus, errorThrown) {
                      options.onSearchError.call(that.element, q, jqXHR, textStatus, errorThrown);
                  });
              }
          },
  
          isBadQuery: function (q) {
              if (!this.options.preventBadQueries){
                  return false;
              }
  
              var badQueries = this.badQueries,
                  i = badQueries.length;
  
              while (i--) {
                  if (q.indexOf(badQueries[i]) === 0) {
                      return true;
                  }
              }
  
              return false;
          },
  
          hide: function () {
              var that = this;
              that.visible = false;
              that.selectedIndex = -1;
              $(that.suggestionsContainer).hide();
              that.signalHint(null);
          },
  
          suggest: function () {
              if (this.suggestions.length === 0) {
                  this.options.showNoSuggestionNotice ? this.noSuggestions() : this.hide();               
                  return;
              }
  
              var that = this,
                  options = that.options,
                  formatResult = options.formatResult,
                  value = that.getQuery(that.currentValue),
                  className = that.classes.suggestion,
                  classSelected = that.classes.selected,
                  container = $(that.suggestionsContainer),
                  noSuggestionsContainer = $(that.noSuggestionsContainer),
                  beforeRender = options.beforeRender,
                  html = '',
                  index;
  
              if (options.triggerSelectOnValidInput) {
                  index = that.findSuggestionIndex(value);
                  if (index !== -1) {
                      that.select(index);
                      return;
                  }
              }
  
              // Build suggestions inner HTML:
              $.each(that.suggestions, function (i, suggestion) {
                  html += '<div class="' + className + '" data-index="' + i + '">' + formatResult(suggestion, value) + '</div>';
              });
  
              this.adjustContainerWidth();      
  
              noSuggestionsContainer.detach();
              container.html(html);
  
              // Select first value by default:
              if (options.autoSelectFirst) {
                  that.selectedIndex = 0;
                  container.children().first().addClass(classSelected);
              }
  
              if ($.isFunction(beforeRender)) {
                  beforeRender.call(that.element, container);
              }
  
              that.fixPosition();
  
              container.show();
              that.visible = true;
  
              that.findBestHint();
          },
  
          noSuggestions: function() {
               var that = this,
                   container = $(that.suggestionsContainer),
                   noSuggestionsContainer = $(that.noSuggestionsContainer);
  
              this.adjustContainerWidth();
  
              // Some explicit steps. Be careful here as it easy to get
              // noSuggestionsContainer removed from DOM if not detached properly.
              noSuggestionsContainer.detach();
              container.empty(); // clean suggestions if any
              container.append(noSuggestionsContainer);
  
              that.fixPosition();
  
              container.show();
              that.visible = true;
          },
  
          adjustContainerWidth: function() {
              var that = this,
                  options = that.options,
                  width,
                  container = $(that.suggestionsContainer);
  
              // If width is auto, adjust width before displaying suggestions,
              // because if instance was created before input had width, it will be zero.
              // Also it adjusts if input width has changed.
              // -2px to account for suggestions border.
              if (options.width === 'auto') {
                  width = that.el.outerWidth() - 2;
                  container.width(width > 0 ? width : 300);
              }
          },
  
          findBestHint: function () {
              var that = this,
                  value = that.el.val().toLowerCase(),
                  bestMatch = null;
  
              if (!value) {
                  return;
              }
  
              $.each(that.suggestions, function (i, suggestion) {
                  var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0;
                  if (foundMatch) {
                      bestMatch = suggestion;
                  }
                  return !foundMatch;
              });
  
              that.signalHint(bestMatch);
          },
  
          signalHint: function (suggestion) {
              var hintValue = '',
                  that = this;
              if (suggestion) {
                  hintValue = that.currentValue + suggestion.value.substr(that.currentValue.length);
              }
              if (that.hintValue !== hintValue) {
                  that.hintValue = hintValue;
                  that.hint = suggestion;
                  (this.options.onHint || $.noop)(hintValue);
              }
          },
  
          verifySuggestionsFormat: function (suggestions) {
              // If suggestions is string array, convert them to supported format:
              if (suggestions.length && typeof suggestions[0] === 'string') {
                  return $.map(suggestions, function (value) {
                      return { value: value, data: null };
                  });
              }
  
              return suggestions;
          },
  
          validateOrientation: function(orientation, fallback) {
              orientation = $.trim(orientation || '').toLowerCase();
  
              if($.inArray(orientation, ['auto', 'bottom', 'top']) === -1){
                  orientation = fallback;
              }
  
              return orientation;
          },
  
          processResponse: function (result, originalQuery, cacheKey) {
              var that = this,
                  options = that.options;
  
              result.suggestions = that.verifySuggestionsFormat(result.suggestions);
  
              // Cache results if cache is not disabled:
              if (!options.noCache) {
                  that.cachedResponse[cacheKey] = result;
                  if (options.preventBadQueries && result.suggestions.length === 0) {
                      that.badQueries.push(originalQuery);
                  }
              }
  
              // Return if originalQuery is not matching current query:
              if (originalQuery !== that.getQuery(that.currentValue)) {
                  return;
              }
  
              that.suggestions = result.suggestions;
              that.suggest();
          },
  
          activate: function (index) {
              var that = this,
                  activeItem,
                  selected = that.classes.selected,
                  container = $(that.suggestionsContainer),
                  children = container.find('.' + that.classes.suggestion);
  
              container.find('.' + selected).removeClass(selected);
  
              that.selectedIndex = index;
  
              if (that.selectedIndex !== -1 && children.length > that.selectedIndex) {
                  activeItem = children.get(that.selectedIndex);
                  $(activeItem).addClass(selected);
                  return activeItem;
              }
  
              return null;
          },
  
          selectHint: function () {
              var that = this,
                  i = $.inArray(that.hint, that.suggestions);
  
              that.select(i);
          },
  
          select: function (i) {
              var that = this;
              that.hide();
              that.onSelect(i);
          },
  
          moveUp: function () {
              var that = this;
  
              if (that.selectedIndex === -1) {
                  return;
              }
  
              if (that.selectedIndex === 0) {
                  $(that.suggestionsContainer).children().first().removeClass(that.classes.selected);
                  that.selectedIndex = -1;
                  that.el.val(that.currentValue);
                  that.findBestHint();
                  return;
              }
  
              that.adjustScroll(that.selectedIndex - 1);
          },
  
          moveDown: function () {
              var that = this;
  
              if (that.selectedIndex === (that.suggestions.length - 1)) {
                  return;
              }
  
              that.adjustScroll(that.selectedIndex + 1);
          },
  
          adjustScroll: function (index) {
              var that = this,
                  activeItem = that.activate(index),
                  offsetTop,
                  upperBound,
                  lowerBound,
                  heightDelta = 25;
  
              if (!activeItem) {
                  return;
              }
  
              offsetTop = activeItem.offsetTop;
              upperBound = $(that.suggestionsContainer).scrollTop();
              lowerBound = upperBound + that.options.maxHeight - heightDelta;
  
              if (offsetTop < upperBound) {
                  $(that.suggestionsContainer).scrollTop(offsetTop);
              } else if (offsetTop > lowerBound) {
                  $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta);
              }
  
              that.el.val(that.getValue(that.suggestions[index].value));
              that.signalHint(null);
          },
  
          onSelect: function (index) {
              var that = this,
                  onSelectCallback = that.options.onSelect,
                  suggestion = that.suggestions[index];
  
              that.currentValue = that.getValue(suggestion.value);
  
              if (that.currentValue !== that.el.val()) {
                  that.el.val(that.currentValue);
              }
  
              that.signalHint(null);
              that.suggestions = [];
              that.selection = suggestion;
  
              if ($.isFunction(onSelectCallback)) {
                  onSelectCallback.call(that.element, suggestion);
              }
          },
  
          getValue: function (value) {
              var that = this,
                  delimiter = that.options.delimiter,
                  currentValue,
                  parts;
  
              if (!delimiter) {
                  return value;
              }
  
              currentValue = that.currentValue;
              parts = currentValue.split(delimiter);
  
              if (parts.length === 1) {
                  return value;
              }
  
              return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value;
          },
  
          dispose: function () {
              var that = this;
              that.el.off('.autocomplete').removeData('autocomplete');
              that.disableKillerFn();
              $(window).off('resize.autocomplete', that.fixPositionCapture);
              $(that.suggestionsContainer).remove();
          }
      };
  
      // Create chainable jQuery plugin:
      $.fn.autocomplete = $.fn.devbridgeAutocomplete = function (options, args) {
          var dataKey = 'autocomplete';
          // If function invoked without argument return
          // instance of the first matched element:
          if (arguments.length === 0) {
              return this.first().data(dataKey);
          }
  
          return this.each(function () {
              var inputElement = $(this),
                  instance = inputElement.data(dataKey);
  
              if (typeof options === 'string') {
                  if (instance && typeof instance[options] === 'function') {
                      instance[options](args);
                  }
              } else {
                  // If instance already exists, destroy it:
                  if (instance && instance.dispose) {
                      instance.dispose();
                  }
                  instance = new Autocomplete(this, options);
                  inputElement.data(dataKey, instance);
              }
          });
      };
  }));
  