/** * 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.addClass(width); var input = $(''); input.attr('type', config.options.type || 'text'); input.appendTo(div); var 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 = $(''); var presubmit = $(''); presubmit.appendTo(container); if (checkboxes.presubmit) { presubmit.prop('checked', true); } container.append(''); var postsubmit = $(''); postsubmit.appendTo(container); if (checkboxes.postsubmit) { postsubmit.prop('checked', true); } container.append(''); 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 = $(''); var col = $(''); col.appendTo(wrapper); Object.keys(filters).forEach(function(key) { col.append(_createInput(key, filters[key])); }); var refreshCol = $(''); var refresh = $('') .append($('cached')) .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 = $(''); var wrapper = $(''); var header = $(''); $('').text(label).appendTo(header); $('').text(value).appendTo(header); header.appendTo(wrapper); var iconWrapper = $(''); $('search').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);