Javascript  |  252行  |  8.43 KB

/**
 * Copyright (c) 2017 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

(function ($) {
  var _inequalityRegex = '(^)(<|>|<=|>=|=)?[ ]*?[0-9]+$';
  var _inequalityHint = 'e.g. 5, >0, <=10';

  function _validate(input, valueSet) {
    var value = input.val();
    if (valueSet.has(value) || !value) {
      input.removeClass('invalid');
    } else {
      input.addClass('invalid');
    }
  }

  function _createInput(key, config) {
    var value = config.value;
    var values = config.options.corpus;
    var displayName = config.displayName;
    var width = config.options.width || 's4';
    var div = $('<div class="input-field col"></div>');
    div.addClass(width);
    var input = $('<input class="filter-input"></input>');
    input.attr('type', config.options.type || 'text');
    input.appendTo(div);
    var label = $('<label></label>').text(displayName).appendTo(div);
    if (value) {
      input.attr('value', value);
      label.addClass('active');
    }
    if (config.options.validate == 'inequality') {
      input.addClass('validate');
      input.attr('pattern', _inequalityRegex);
      input.attr('placeholder', _inequalityHint);
      label.addClass('active');
    }
    input.focusout(function() {
      config.value = input.val();
    });
    if (values && values.length > 0) {
      var valueSet = new Set(values);
      input.sizedAutocomplete({
        source: values,
        classes: {
          'ui-autocomplete': 'card search-bar-menu'
        }
      });
      input.focusout(function() {
        _validate(input, valueSet);
      });
    }
    if (values && values.length > 0 && value) {
      _validate(input, valueSet);
    }
    return div;
  }

  function _verifyCheckboxes(checkboxes, refreshObject) {
    var oneChecked = checkboxes.presubmit || checkboxes.postsubmit;
    if (!oneChecked) {
      refreshObject.addClass('disabled');
    } else {
      refreshObject.removeClass('disabled');
    }
  }

  function _createRunTypeBoxes(checkboxes, refreshObject) {
    var container = $('<div class="run-type-wrapper col s12"></div>');
    var presubmit = $('<input type="checkbox" id="presubmit"></input>');
    presubmit.appendTo(container);
    if (checkboxes.presubmit) {
      presubmit.prop('checked', true);
    }
    container.append('<label for="presubmit">Presubmit</label>');
    var postsubmit = $('<input type="checkbox" id="postsubmit"></input>');
    postsubmit.appendTo(container);
    if (checkboxes.postsubmit) {
      postsubmit.prop('checked', true);
    }
    container.append('<label for="postsubmit">Postsubmit</label>');
    presubmit.change(function() {
      checkboxes.presubmit = presubmit.prop('checked');
      _verifyCheckboxes(checkboxes, refreshObject);
    });
    postsubmit.change(function() {
      checkboxes.postsubmit = postsubmit.prop('checked');
      _verifyCheckboxes(checkboxes, refreshObject);
    });
    return container;
  }

  function _expand(
      container, filters, checkboxes, onRefreshCallback, animate=true) {
    var wrapper = $('<div class="search-wrapper"></div>');
    var col = $('<div class="col s9"></div>');
    col.appendTo(wrapper);
    Object.keys(filters).forEach(function(key) {
      col.append(_createInput(key, filters[key]));
    });
    var refreshCol = $('<div class="col s3 refresh-wrapper"></div>');
    var refresh = $('<a class="btn-floating btn-medium red right waves-effect waves-light"></a>')
      .append($('<i class="medium material-icons">cached</i>'))
      .appendTo(refreshCol);
    refresh.click(onRefreshCallback);
    refreshCol.appendTo(wrapper);
    if (Object.keys(checkboxes).length > 0) {
      col.append(_createRunTypeBoxes(checkboxes, refresh));
    }
    if (animate) {
      wrapper.hide().appendTo(container).slideDown({
        duration: 200,
        easing: "easeOutQuart",
        queue: false
      });
    } else {
      wrapper.appendTo(container);
    }
    container.addClass('expanded')
  }

  function _renderHeader(
      container, label, value, filters, checkboxes, expand, onRefreshCallback) {
    var div = $('<div class="row card search-bar"></div>');
    var wrapper = $('<div class="header-wrapper"></div>');
    var header = $('<h5 class="section-header"></h5>');
    $('<b></b>').text(label).appendTo(header);
    $('<span></span>').text(value).appendTo(header);
    header.appendTo(wrapper);
    var iconWrapper = $('<div class="search-icon-wrapper"></div>');
    $('<i class="material-icons">search</i>').appendTo(iconWrapper);
    iconWrapper.appendTo(wrapper);
    wrapper.appendTo(div);
    if (expand) {
      _expand(div, filters, checkboxes, onRefreshCallback, false);
    } else {
      var expanded = false;
      iconWrapper.click(function() {
        if (expanded) return;
        expanded = true;
        _expand(div, filters, checkboxes, onRefreshCallback);
      });
    }
    div.appendTo(container);
  }

  function _addFilter(filters, displayName, keyName, options, defaultValue) {
    filters[keyName] = {};
    filters[keyName].displayName = displayName;
    filters[keyName].value = defaultValue;
    filters[keyName].options = options;
  }

  function _getOptionString(filters, checkboxes) {
    var args = Object.keys(filters).reduce(function(acc, key) {
      if (filters[key].value) {
        return acc + '&' + key + '=' + encodeURIComponent(filters[key].value);
      }
      return acc;
    }, '');
    if (checkboxes.presubmit != undefined && checkboxes.presubmit) {
      args += '&showPresubmit='
    }
    if (checkboxes.postsubmit != undefined && checkboxes.postsubmit) {
      args += '&showPostsubmit='
    }
    return args;
  }

  /**
   * Create a search header element.
   * @param label The header label.
   * @param value The value to display next to the label.
   * @param onRefreshCallback The function to call on refresh.
   */
  $.fn.createSearchHeader = function(label, value, onRefreshCallback) {
    var self = $(this);
    $.widget('custom.sizedAutocomplete', $.ui.autocomplete, {
      _resizeMenu : function() {
        this.menu.element.outerWidth($('.search-bar .filter-input').width());
      }
    });
    var filters = {};
    var checkboxes = {};
    var expandOnRender = false;
    var displayed = false;
    return {
      /**
       * Add a filter to the display.
       * @param displayName The input placeholder/label text.
       * @param keyName The URL key to use for the filter options.
       * @param options A dict of additional options (e.g. width, type).
       * @param defaultValue A default filter value.
       */
      addFilter : function(displayName, keyName, options, defaultValue) {
        if (displayed) return;
        _addFilter(filters, displayName, keyName, options, defaultValue);
        if (defaultValue) expandOnRender = true;
      },
      /**
       * Enable run type checkboxes in the filter options.
       *
       * This will display two checkboxes for selecting pre-/postsubmit runs.
       * @param showPresubmit True if presubmit runs are selected.
       * @param showPostsubmit True if postsubmit runs are selected.
       *
       */
      addRunTypeCheckboxes: function(showPresubmit, showPostsubmit) {
        if (displayed) return;
        checkboxes['presubmit'] = showPresubmit;
        checkboxes['postsubmit'] = showPostsubmit;
        if (!showPostsubmit || showPresubmit) {
          expandOnRender = true;
        }
      },
      /**
       * Display the created search bar.
       *
       * This must be called after filters have been added. After displaying, no
       * modifications to the filter options will take effect.
       */
      display : function() {
        displayed = true;
        _renderHeader(
          self, label, value, filters, checkboxes, expandOnRender,
          onRefreshCallback);
      },
      /**
       * Get the URL arguments string for the current set of filters.
       * @returns a URI-encoded component with the search bar keys and values.
       */
      args : function () {
        return _getOptionString(filters, checkboxes);
      }
    }
  }

})(jQuery);