Home | History | Annotate | Download | only in js
      1 /**
      2  * Copyright (c) 2017 Google Inc. All Rights Reserved.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you
      5  * may not use this file except in compliance with the License. You may
      6  * obtain a copy of the License at
      7  *
      8  *   http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
     13  * implied. See the License for the specific language governing
     14  * permissions and limitations under the License.
     15  */
     16 
     17 (function ($) {
     18   var _inequalityRegex = '(^)(<|>|<=|>=|=)?[ ]*?[0-9]+$';
     19   var _inequalityHint = 'e.g. 5, >0, <=10';
     20 
     21   function _validate(input, valueSet) {
     22     var value = input.val();
     23     if (valueSet.has(value) || !value) {
     24       input.removeClass('invalid');
     25     } else {
     26       input.addClass('invalid');
     27     }
     28   }
     29 
     30   function _createInput(key, config) {
     31     var value = config.value;
     32     var values = config.options.corpus;
     33     var displayName = config.displayName;
     34     var width = config.options.width || 's4';
     35     var div = $('<div class="input-field col"></div>');
     36     div.addClass(width);
     37     var input = $('<input class="filter-input"></input>');
     38     input.attr('type', config.options.type || 'text');
     39     input.appendTo(div);
     40     var label = $('<label></label>').text(displayName).appendTo(div);
     41     if (value) {
     42       input.attr('value', value);
     43       label.addClass('active');
     44     }
     45     if (config.options.validate == 'inequality') {
     46       input.addClass('validate');
     47       input.attr('pattern', _inequalityRegex);
     48       input.attr('placeholder', _inequalityHint);
     49       label.addClass('active');
     50     }
     51     input.focusout(function() {
     52       config.value = input.val();
     53     });
     54     if (values && values.length > 0) {
     55       var valueSet = new Set(values);
     56       input.sizedAutocomplete({
     57         source: values,
     58         classes: {
     59           'ui-autocomplete': 'card search-bar-menu'
     60         }
     61       });
     62       input.focusout(function() {
     63         _validate(input, valueSet);
     64       });
     65     }
     66     if (values && values.length > 0 && value) {
     67       _validate(input, valueSet);
     68     }
     69     return div;
     70   }
     71 
     72   function _verifyCheckboxes(checkboxes, refreshObject) {
     73     var oneChecked = checkboxes.presubmit || checkboxes.postsubmit;
     74     if (!oneChecked) {
     75       refreshObject.addClass('disabled');
     76     } else {
     77       refreshObject.removeClass('disabled');
     78     }
     79   }
     80 
     81   function _createRunTypeBoxes(checkboxes, refreshObject) {
     82     var container = $('<div class="run-type-wrapper col s12"></div>');
     83     var presubmit = $('<input type="checkbox" id="presubmit"></input>');
     84     presubmit.appendTo(container);
     85     if (checkboxes.presubmit) {
     86       presubmit.prop('checked', true);
     87     }
     88     container.append('<label for="presubmit">Presubmit</label>');
     89     var postsubmit = $('<input type="checkbox" id="postsubmit"></input>');
     90     postsubmit.appendTo(container);
     91     if (checkboxes.postsubmit) {
     92       postsubmit.prop('checked', true);
     93     }
     94     container.append('<label for="postsubmit">Postsubmit</label>');
     95     presubmit.change(function() {
     96       checkboxes.presubmit = presubmit.prop('checked');
     97       _verifyCheckboxes(checkboxes, refreshObject);
     98     });
     99     postsubmit.change(function() {
    100       checkboxes.postsubmit = postsubmit.prop('checked');
    101       _verifyCheckboxes(checkboxes, refreshObject);
    102     });
    103     return container;
    104   }
    105 
    106   function _expand(
    107       container, filters, checkboxes, onRefreshCallback, animate=true) {
    108     var wrapper = $('<div class="search-wrapper"></div>');
    109     var col = $('<div class="col s9"></div>');
    110     col.appendTo(wrapper);
    111     Object.keys(filters).forEach(function(key) {
    112       col.append(_createInput(key, filters[key]));
    113     });
    114     var refreshCol = $('<div class="col s3 refresh-wrapper"></div>');
    115     var refresh = $('<a class="btn-floating btn-medium red right waves-effect waves-light"></a>')
    116       .append($('<i class="medium material-icons">cached</i>'))
    117       .appendTo(refreshCol);
    118     refresh.click(onRefreshCallback);
    119     refreshCol.appendTo(wrapper);
    120     if (Object.keys(checkboxes).length > 0) {
    121       col.append(_createRunTypeBoxes(checkboxes, refresh));
    122     }
    123     if (animate) {
    124       wrapper.hide().appendTo(container).slideDown({
    125         duration: 200,
    126         easing: "easeOutQuart",
    127         queue: false
    128       });
    129     } else {
    130       wrapper.appendTo(container);
    131     }
    132     container.addClass('expanded')
    133   }
    134 
    135   function _renderHeader(
    136       container, label, value, filters, checkboxes, expand, onRefreshCallback) {
    137     var div = $('<div class="row card search-bar"></div>');
    138     var wrapper = $('<div class="header-wrapper"></div>');
    139     var header = $('<h5 class="section-header"></h5>');
    140     $('<b></b>').text(label).appendTo(header);
    141     $('<span></span>').text(value).appendTo(header);
    142     header.appendTo(wrapper);
    143     var iconWrapper = $('<div class="search-icon-wrapper"></div>');
    144     $('<i class="material-icons">search</i>').appendTo(iconWrapper);
    145     iconWrapper.appendTo(wrapper);
    146     wrapper.appendTo(div);
    147     if (expand) {
    148       _expand(div, filters, checkboxes, onRefreshCallback, false);
    149     } else {
    150       var expanded = false;
    151       iconWrapper.click(function() {
    152         if (expanded) return;
    153         expanded = true;
    154         _expand(div, filters, checkboxes, onRefreshCallback);
    155       });
    156     }
    157     div.appendTo(container);
    158   }
    159 
    160   function _addFilter(filters, displayName, keyName, options, defaultValue) {
    161     filters[keyName] = {};
    162     filters[keyName].displayName = displayName;
    163     filters[keyName].value = defaultValue;
    164     filters[keyName].options = options;
    165   }
    166 
    167   function _getOptionString(filters, checkboxes) {
    168     var args = Object.keys(filters).reduce(function(acc, key) {
    169       if (filters[key].value) {
    170         return acc + '&' + key + '=' + encodeURIComponent(filters[key].value);
    171       }
    172       return acc;
    173     }, '');
    174     if (checkboxes.presubmit != undefined && checkboxes.presubmit) {
    175       args += '&showPresubmit='
    176     }
    177     if (checkboxes.postsubmit != undefined && checkboxes.postsubmit) {
    178       args += '&showPostsubmit='
    179     }
    180     return args;
    181   }
    182 
    183   /**
    184    * Create a search header element.
    185    * @param label The header label.
    186    * @param value The value to display next to the label.
    187    * @param onRefreshCallback The function to call on refresh.
    188    */
    189   $.fn.createSearchHeader = function(label, value, onRefreshCallback) {
    190     var self = $(this);
    191     $.widget('custom.sizedAutocomplete', $.ui.autocomplete, {
    192       _resizeMenu : function() {
    193         this.menu.element.outerWidth($('.search-bar .filter-input').width());
    194       }
    195     });
    196     var filters = {};
    197     var checkboxes = {};
    198     var expandOnRender = false;
    199     var displayed = false;
    200     return {
    201       /**
    202        * Add a filter to the display.
    203        * @param displayName The input placeholder/label text.
    204        * @param keyName The URL key to use for the filter options.
    205        * @param options A dict of additional options (e.g. width, type).
    206        * @param defaultValue A default filter value.
    207        */
    208       addFilter : function(displayName, keyName, options, defaultValue) {
    209         if (displayed) return;
    210         _addFilter(filters, displayName, keyName, options, defaultValue);
    211         if (defaultValue) expandOnRender = true;
    212       },
    213       /**
    214        * Enable run type checkboxes in the filter options.
    215        *
    216        * This will display two checkboxes for selecting pre-/postsubmit runs.
    217        * @param showPresubmit True if presubmit runs are selected.
    218        * @param showPostsubmit True if postsubmit runs are selected.
    219        *
    220        */
    221       addRunTypeCheckboxes: function(showPresubmit, showPostsubmit) {
    222         if (displayed) return;
    223         checkboxes['presubmit'] = showPresubmit;
    224         checkboxes['postsubmit'] = showPostsubmit;
    225         if (!showPostsubmit || showPresubmit) {
    226           expandOnRender = true;
    227         }
    228       },
    229       /**
    230        * Display the created search bar.
    231        *
    232        * This must be called after filters have been added. After displaying, no
    233        * modifications to the filter options will take effect.
    234        */
    235       display : function() {
    236         displayed = true;
    237         _renderHeader(
    238           self, label, value, filters, checkboxes, expandOnRender,
    239           onRefreshCallback);
    240       },
    241       /**
    242        * Get the URL arguments string for the current set of filters.
    243        * @returns a URI-encoded component with the search bar keys and values.
    244        */
    245       args : function () {
    246         return _getOptionString(filters, checkboxes);
    247       }
    248     }
    249   }
    250 
    251 })(jQuery);
    252