search_autocomplete.js.es6 13.8 KB
Newer Older
1
/* eslint-disable comma-dangle, no-return-assign, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-unused-vars, no-cond-assign, consistent-return, object-shorthand, prefer-arrow-callback, func-names, space-before-function-paren, prefer-template, quotes, class-methods-use-this, no-unused-expressions, no-sequences, wrap-iife, no-lonely-if, no-else-return, no-param-reassign, vars-on-top, max-len */
2

3
((global) => {
4 5 6 7 8 9 10 11 12
  const KEYCODE = {
    ESCAPE: 27,
    BACKSPACE: 8,
    ENTER: 13,
    UP: 38,
    DOWN: 40
  };

  class SearchAutocomplete {
13
    constructor({ wrap, optsEl, autocompletePath, projectId, projectRef } = {}) {
14
      this.bindEventContext();
15
      this.wrap = wrap || $('.search');
16 17 18 19 20
      this.optsEl = optsEl || this.wrap.find('.search-autocomplete-opts');
      this.autocompletePath = autocompletePath || this.optsEl.data('autocomplete-path');
      this.projectId = projectId || (this.optsEl.data('autocomplete-project-id') || '');
      this.projectRef = projectRef || (this.optsEl.data('autocomplete-project-ref') || '');
      this.dropdown = this.wrap.find('.dropdown');
F
Fatih Acet 已提交
21 22 23 24 25 26 27 28 29 30
      this.dropdownContent = this.dropdown.find('.dropdown-content');
      this.locationBadgeEl = this.getElement('.location-badge');
      this.scopeInputEl = this.getElement('#scope');
      this.searchInput = this.getElement('.search-input');
      this.projectInputEl = this.getElement('#search_project_id');
      this.groupInputEl = this.getElement('#group_id');
      this.searchCodeInputEl = this.getElement('#search_code');
      this.repositoryInputEl = this.getElement('#repository_ref');
      this.clearInput = this.getElement('.js-clear-input');
      this.saveOriginalState();
31
      // Only when user is logged in
F
Fatih Acet 已提交
32 33 34 35 36 37 38 39
      if (gon.current_user_id) {
        this.createAutocomplete();
      }
      this.searchInput.addClass('disabled');
      this.saveTextLength();
      this.bindEvents();
    }

40
    // Finds an element inside wrapper element
41 42 43 44 45 46 47 48
    bindEventContext() {
      this.onSearchInputBlur = this.onSearchInputBlur.bind(this);
      this.onClearInputClick = this.onClearInputClick.bind(this);
      this.onSearchInputFocus = this.onSearchInputFocus.bind(this);
      this.onSearchInputClick = this.onSearchInputClick.bind(this);
      this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this);
      this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this);
    }
49
    getElement(selector) {
F
Fatih Acet 已提交
50
      return this.wrap.find(selector);
51
    }
F
Fatih Acet 已提交
52

53
    saveOriginalState() {
F
Fatih Acet 已提交
54
      return this.originalState = this.serializeState();
55
    }
F
Fatih Acet 已提交
56

57
    saveTextLength() {
F
Fatih Acet 已提交
58
      return this.lastTextLength = this.searchInput.val().length;
59
    }
F
Fatih Acet 已提交
60

61
    createAutocomplete() {
F
Fatih Acet 已提交
62 63 64 65 66 67 68 69 70 71 72 73 74 75
      return this.searchInput.glDropdown({
        filterInputBlur: false,
        filterable: true,
        filterRemote: true,
        highlight: true,
        enterCallback: false,
        filterInput: 'input#search',
        search: {
          fields: ['text']
        },
        data: this.getData.bind(this),
        selectable: true,
        clicked: this.onClick.bind(this)
      });
76
    }
F
Fatih Acet 已提交
77

78
    getData(term, callback) {
F
Fatih Acet 已提交
79 80 81 82 83 84 85 86 87
      var _this, contents, jqXHR;
      _this = this;
      if (!term) {
        if (contents = this.getCategoryContents()) {
          this.searchInput.data('glDropdown').filter.options.callback(contents);
          this.enableAutocomplete();
        }
        return;
      }
88
      // Prevent multiple ajax calls
F
Fatih Acet 已提交
89 90 91 92 93 94 95 96 97 98
      if (this.loadingSuggestions) {
        return;
      }
      this.loadingSuggestions = true;
      return jqXHR = $.get(this.autocompletePath, {
        project_id: this.projectId,
        project_ref: this.projectRef,
        term: term
      }, function(response) {
        var data, firstCategory, i, lastCategory, len, suggestion;
99
        // Hide dropdown menu if no suggestions returns
F
Fatih Acet 已提交
100 101 102 103 104
        if (!response.length) {
          _this.disableAutocomplete();
          return;
        }
        data = [];
105
        // List results
F
Fatih Acet 已提交
106
        firstCategory = true;
107
        for (i = 0, len = response.length; i < len; i += 1) {
F
Fatih Acet 已提交
108
          suggestion = response[i];
109
          // Add group header before list each group
F
Fatih Acet 已提交
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
          if (lastCategory !== suggestion.category) {
            if (!firstCategory) {
              data.push('separator');
            }
            if (firstCategory) {
              firstCategory = false;
            }
            data.push({
              header: suggestion.category
            });
            lastCategory = suggestion.category;
          }
          data.push({
            id: (suggestion.category.toLowerCase()) + "-" + suggestion.id,
            category: suggestion.category,
            text: suggestion.label,
            url: suggestion.url
          });
        }
129
        // Add option to proceed with the search
F
Fatih Acet 已提交
130 131 132 133 134 135 136 137 138 139 140
        if (data.length) {
          data.push('separator');
          data.push({
            text: "Result name contains \"" + term + "\"",
            url: "/search?search=" + term + "&project_id=" + (_this.projectInputEl.val()) + "&group_id=" + (_this.groupInputEl.val())
          });
        }
        return callback(data);
      }).always(function() {
        return _this.loadingSuggestions = false;
      });
141
    }
F
Fatih Acet 已提交
142

143
    getCategoryContents() {
C
Clement Ho 已提交
144
      var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, userName, utils;
F
Fatih Acet 已提交
145
      userId = gon.current_user_id;
C
Clement Ho 已提交
146
      userName = gon.current_username;
F
Fatih Acet 已提交
147 148 149 150 151 152 153 154 155 156 157 158 159 160
      utils = gl.utils, projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions;
      if (utils.isInGroupsPage() && groupOptions) {
        options = groupOptions[utils.getGroupSlug()];
      } else if (utils.isInProjectPage() && projectOptions) {
        options = projectOptions[utils.getProjectSlug()];
      } else if (dashboardOptions) {
        options = dashboardOptions;
      }
      issuesPath = options.issuesPath, mrPath = options.mrPath, name = options.name;
      items = [
        {
          header: "" + name
        }, {
          text: 'Issues assigned to me',
C
Clement Ho 已提交
161
          url: issuesPath + "/?assignee_username=" + userName
F
Fatih Acet 已提交
162 163
        }, {
          text: "Issues I've created",
C
Clement Ho 已提交
164
          url: issuesPath + "/?author_username=" + userName
F
Fatih Acet 已提交
165 166 167 168 169 170 171 172 173 174 175 176
        }, 'separator', {
          text: 'Merge requests assigned to me',
          url: mrPath + "/?assignee_id=" + userId
        }, {
          text: "Merge requests I've created",
          url: mrPath + "/?author_id=" + userId
        }
      ];
      if (!name) {
        items.splice(0, 1);
      }
      return items;
177
    }
F
Fatih Acet 已提交
178

179
    serializeState() {
F
Fatih Acet 已提交
180
      return {
181
        // Search Criteria
F
Fatih Acet 已提交
182 183 184 185 186
        search_project_id: this.projectInputEl.val(),
        group_id: this.groupInputEl.val(),
        search_code: this.searchCodeInputEl.val(),
        repository_ref: this.repositoryInputEl.val(),
        scope: this.scopeInputEl.val(),
187
        // Location badge
F
Fatih Acet 已提交
188 189
        _location: this.locationBadgeEl.text()
      };
190
    }
F
Fatih Acet 已提交
191

192
    bindEvents() {
F
Fatih Acet 已提交
193 194 195 196 197 198 199 200 201 202 203
      this.searchInput.on('keydown', this.onSearchInputKeyDown);
      this.searchInput.on('keyup', this.onSearchInputKeyUp);
      this.searchInput.on('click', this.onSearchInputClick);
      this.searchInput.on('focus', this.onSearchInputFocus);
      this.searchInput.on('blur', this.onSearchInputBlur);
      this.clearInput.on('click', this.onClearInputClick);
      return this.locationBadgeEl.on('click', (function(_this) {
        return function() {
          return _this.searchInput.focus();
        };
      })(this));
204
    }
F
Fatih Acet 已提交
205

206
    enableAutocomplete() {
F
Fatih Acet 已提交
207
      var _this;
208
      // No need to enable anything if user is not logged in
F
Fatih Acet 已提交
209 210 211 212 213 214 215 216 217
      if (!gon.current_user_id) {
        return;
      }
      if (!this.dropdown.hasClass('open')) {
        _this = this;
        this.loadingSuggestions = false;
        this.dropdown.addClass('open').trigger('shown.bs.dropdown');
        return this.searchInput.removeClass('disabled');
      }
218
    }
F
Fatih Acet 已提交
219

220
      // Saves last length of the entered text
221
    onSearchInputKeyDown() {
F
Fatih Acet 已提交
222
      return this.saveTextLength();
223
    }
F
Fatih Acet 已提交
224

225
    onSearchInputKeyUp(e) {
F
Fatih Acet 已提交
226 227
      switch (e.keyCode) {
        case KEYCODE.BACKSPACE:
228
          // when trying to remove the location badge
F
Fatih Acet 已提交
229 230 231
          if (this.lastTextLength === 0 && this.badgePresent()) {
            this.removeLocationBadge();
          }
232
          // When removing the last character and no badge is present
F
Fatih Acet 已提交
233 234 235
          if (this.lastTextLength === 1) {
            this.disableAutocomplete();
          }
236
          // When removing any character from existin value
F
Fatih Acet 已提交
237 238 239 240 241 242 243
          if (this.lastTextLength > 1) {
            this.enableAutocomplete();
          }
          break;
        case KEYCODE.ESCAPE:
          this.restoreOriginalState();
          break;
244
        case KEYCODE.ENTER:
L
Luke Bennett 已提交
245
          this.disableAutocomplete();
246
          break;
L
Luke Bennett 已提交
247
        case KEYCODE.UP:
248
        case KEYCODE.DOWN:
L
Luke Bennett 已提交
249
          return;
F
Fatih Acet 已提交
250
        default:
251 252
          // Handle the case when deleting the input value other than backspace
          // e.g. Pressing ctrl + backspace or ctrl + x
F
Fatih Acet 已提交
253 254 255
          if (this.searchInput.val() === '') {
            this.disableAutocomplete();
          } else {
256
            // We should display the menu only when input is not empty
F
Fatih Acet 已提交
257 258 259 260 261 262
            if (e.keyCode !== KEYCODE.ENTER) {
              this.enableAutocomplete();
            }
          }
      }
      this.wrap.toggleClass('has-value', !!e.target.value);
263
    }
F
Fatih Acet 已提交
264

265
    // Avoid falsy value to be returned
266
    onSearchInputClick(e) {
F
Fatih Acet 已提交
267
      return e.stopImmediatePropagation();
268
    }
F
Fatih Acet 已提交
269

270
    onSearchInputFocus() {
F
Fatih Acet 已提交
271 272 273 274 275
      this.isFocused = true;
      this.wrap.addClass('search-active');
      if (this.getValue() === '') {
        return this.getData();
      }
276
    }
F
Fatih Acet 已提交
277

278
    getValue() {
F
Fatih Acet 已提交
279
      return this.searchInput.val();
280
    }
F
Fatih Acet 已提交
281

282
    onClearInputClick(e) {
F
Fatih Acet 已提交
283 284
      e.preventDefault();
      return this.searchInput.val('').focus();
285
    }
F
Fatih Acet 已提交
286

287
    onSearchInputBlur(e) {
F
Fatih Acet 已提交
288 289
      this.isFocused = false;
      this.wrap.removeClass('search-active');
290
      // If input is blank then restore state
F
Fatih Acet 已提交
291 292 293
      if (this.searchInput.val() === '') {
        return this.restoreOriginalState();
      }
294
    }
F
Fatih Acet 已提交
295

296
    addLocationBadge(item) {
F
Fatih Acet 已提交
297 298 299 300 301 302
      var badgeText, category, value;
      category = item.category != null ? item.category + ": " : '';
      value = item.value != null ? item.value : '';
      badgeText = "" + category + value;
      this.locationBadgeEl.text(badgeText).show();
      return this.wrap.addClass('has-location-badge');
303
    }
F
Fatih Acet 已提交
304

305
    hasLocationBadge() {
F
Fatih Acet 已提交
306
      return this.wrap.is('.has-location-badge');
307
    }
F
Fatih Acet 已提交
308

309
    restoreOriginalState() {
F
Fatih Acet 已提交
310 311
      var i, input, inputs, len;
      inputs = Object.keys(this.originalState);
312
      for (i = 0, len = inputs.length; i < len; i += 1) {
F
Fatih Acet 已提交
313 314 315 316 317 318 319 320 321 322
        input = inputs[i];
        this.getElement("#" + input).val(this.originalState[input]);
      }
      if (this.originalState._location === '') {
        return this.locationBadgeEl.hide();
      } else {
        return this.addLocationBadge({
          value: this.originalState._location
        });
      }
323
    }
F
Fatih Acet 已提交
324

325
    badgePresent() {
F
Fatih Acet 已提交
326
      return this.locationBadgeEl.length;
327
    }
F
Fatih Acet 已提交
328

329
    resetSearchState() {
F
Fatih Acet 已提交
330 331 332
      var i, input, inputs, len, results;
      inputs = Object.keys(this.originalState);
      results = [];
333
      for (i = 0, len = inputs.length; i < len; i += 1) {
F
Fatih Acet 已提交
334
        input = inputs[i];
335
        // _location isnt a input
F
Fatih Acet 已提交
336 337 338 339 340 341
        if (input === '_location') {
          break;
        }
        results.push(this.getElement("#" + input).val(''));
      }
      return results;
342
    }
F
Fatih Acet 已提交
343

344
    removeLocationBadge() {
F
Fatih Acet 已提交
345 346 347 348
      this.locationBadgeEl.hide();
      this.resetSearchState();
      this.wrap.removeClass('has-location-badge');
      return this.disableAutocomplete();
349
    }
F
Fatih Acet 已提交
350

351
    disableAutocomplete() {
352 353 354 355 356
      if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) {
        this.searchInput.addClass('disabled');
        this.dropdown.removeClass('open').trigger('hidden.bs.dropdown');
        this.restoreMenu();
      }
357
    }
F
Fatih Acet 已提交
358

359
    restoreMenu() {
F
Fatih Acet 已提交
360 361 362
      var html;
      html = "<ul> <li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li> </ul>";
      return this.dropdownContent.html(html);
363
    }
F
Fatih Acet 已提交
364

365
    onClick(item, $el, e) {
F
Fatih Acet 已提交
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
      if (location.pathname.indexOf(item.url) !== -1) {
        e.preventDefault();
        if (!this.badgePresent) {
          if (item.category === 'Projects') {
            this.projectInputEl.val(item.id);
            this.addLocationBadge({
              value: 'This project'
            });
          }
          if (item.category === 'Groups') {
            this.groupInputEl.val(item.id);
            this.addLocationBadge({
              value: 'This group'
            });
          }
        }
        $el.removeClass('is-active');
        this.disableAutocomplete();
        return this.searchInput.val('').focus();
      }
386
    }
387
  }
F
Fatih Acet 已提交
388

389
  global.SearchAutocomplete = SearchAutocomplete;
F
Fatih Acet 已提交
390

391
  $(function() {
A
Alfredo Sumaran 已提交
392 393 394
    var $projectOptionsDataEl = $('.js-search-project-options');
    var $groupOptionsDataEl = $('.js-search-group-options');
    var $dashboardOptionsDataEl = $('.js-search-dashboard-options');
395

A
Alfredo Sumaran 已提交
396
    if ($projectOptionsDataEl.length) {
397 398
      gl.projectOptions = gl.projectOptions || {};

A
Alfredo Sumaran 已提交
399
      var projectPath = $projectOptionsDataEl.data('project-path');
400 401

      gl.projectOptions[projectPath] = {
A
Alfredo Sumaran 已提交
402 403 404
        name: $projectOptionsDataEl.data('name'),
        issuesPath: $projectOptionsDataEl.data('issues-path'),
        mrPath: $projectOptionsDataEl.data('mr-path')
405 406
      };
    }
A
Alfredo Sumaran 已提交
407 408

    if ($groupOptionsDataEl.length) {
409
      gl.groupOptions = gl.groupOptions || {};
410

A
Alfredo Sumaran 已提交
411
      var groupPath = $groupOptionsDataEl.data('group-path');
412

413
      gl.groupOptions[groupPath] = {
A
Alfredo Sumaran 已提交
414 415 416
        name: $groupOptionsDataEl.data('name'),
        issuesPath: $groupOptionsDataEl.data('issues-path'),
        mrPath: $groupOptionsDataEl.data('mr-path')
417 418
      };
    }
419

A
Alfredo Sumaran 已提交
420
    if ($dashboardOptionsDataEl.length) {
421
      gl.dashboardOptions = {
A
Alfredo Sumaran 已提交
422 423
        issuesPath: $dashboardOptionsDataEl.data('issues-path'),
        mrPath: $dashboardOptionsDataEl.data('mr-path')
424 425 426
      };
    }
  });
427
})(window.gl || (window.gl = {}));