blob: 4f108c425146288447bb120c451dab89b0a9178f [file] [log] [blame]
/**
* 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);