search_autocomplete.js.es6 13.9 KB
Newer Older
1 2
/* 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, no-plusplus, 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, padded-blocks, no-extra-semi, indent, max-len */

3
((global) => {
4 5 6 7 8 9 10 11 12 13

  const KEYCODE = {
    ESCAPE: 27,
    BACKSPACE: 8,
    ENTER: 13,
    UP: 38,
    DOWN: 40
  };

  class SearchAutocomplete {
14
    constructor({ wrap, optsEl, autocompletePath, projectId, projectRef } = {}) {
15
      this.bindEventContext();
16
      this.wrap = wrap || $('.search');
17 18 19 20 21
      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 已提交
22 23 24 25 26 27 28 29 30 31
      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();
32
      // Only when user is logged in
F
Fatih Acet 已提交
33 34 35 36 37 38 39 40
      if (gon.current_user_id) {
        this.createAutocomplete();
      }
      this.searchInput.addClass('disabled');
      this.saveTextLength();
      this.bindEvents();
    }

41
    // Finds an element inside wrapper element
42 43 44 45 46 47 48 49
    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);
    }
50
    getElement(selector) {
F
Fatih Acet 已提交
51
      return this.wrap.find(selector);
52
    }
F
Fatih Acet 已提交
53

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

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

62
    createAutocomplete() {
F
Fatih Acet 已提交
63 64 65 66 67 68 69 70 71 72 73 74 75 76
      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)
      });
77
    }
F
Fatih Acet 已提交
78

79
    getData(term, callback) {
F
Fatih Acet 已提交
80 81 82 83 84 85 86 87 88
      var _this, contents, jqXHR;
      _this = this;
      if (!term) {
        if (contents = this.getCategoryContents()) {
          this.searchInput.data('glDropdown').filter.options.callback(contents);
          this.enableAutocomplete();
        }
        return;
      }
89
      // Prevent multiple ajax calls
F
Fatih Acet 已提交
90 91 92 93 94 95 96 97 98 99
      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;
100
        // Hide dropdown menu if no suggestions returns
F
Fatih Acet 已提交
101 102 103 104 105
        if (!response.length) {
          _this.disableAutocomplete();
          return;
        }
        data = [];
106
        // List results
F
Fatih Acet 已提交
107 108 109
        firstCategory = true;
        for (i = 0, len = response.length; i < len; i++) {
          suggestion = response[i];
110
          // Add group header before list each group
F
Fatih Acet 已提交
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
          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
          });
        }
130
        // Add option to proceed with the search
F
Fatih Acet 已提交
131 132 133 134 135 136 137 138 139 140 141
        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;
      });
142
    }
F
Fatih Acet 已提交
143

144
    getCategoryContents() {
C
Clement Ho 已提交
145
      var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, userName, utils;
F
Fatih Acet 已提交
146
      userId = gon.current_user_id;
C
Clement Ho 已提交
147
      userName = gon.current_username;
F
Fatih Acet 已提交
148 149 150 151 152 153 154 155 156 157 158 159 160 161
      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 已提交
162
          url: issuesPath + "/?assignee_username=" + userName
F
Fatih Acet 已提交
163 164
        }, {
          text: "Issues I've created",
C
Clement Ho 已提交
165
          url: issuesPath + "/?author_username=" + userName
F
Fatih Acet 已提交
166 167 168 169 170 171 172 173 174 175 176 177
        }, '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;
178
    }
F
Fatih Acet 已提交
179

180
    serializeState() {
F
Fatih Acet 已提交
181
      return {
182
        // Search Criteria
F
Fatih Acet 已提交
183 184 185 186 187
        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(),
188
        // Location badge
F
Fatih Acet 已提交
189 190
        _location: this.locationBadgeEl.text()
      };
191
    }
F
Fatih Acet 已提交
192

193
    bindEvents() {
F
Fatih Acet 已提交
194 195 196 197 198 199 200 201 202 203 204
      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));
205
    }
F
Fatih Acet 已提交
206

207
    enableAutocomplete() {
F
Fatih Acet 已提交
208
      var _this;
209
      // No need to enable anything if user is not logged in
F
Fatih Acet 已提交
210 211 212 213 214 215 216 217 218 219 220
      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');
      }
    };

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

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

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

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

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

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

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

297
    addLocationBadge(item) {
F
Fatih Acet 已提交
298 299 300 301 302 303
      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');
304
    }
F
Fatih Acet 已提交
305

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

310
    restoreOriginalState() {
F
Fatih Acet 已提交
311 312 313 314 315 316 317 318 319 320 321 322 323
      var i, input, inputs, len;
      inputs = Object.keys(this.originalState);
      for (i = 0, len = inputs.length; i < len; i++) {
        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
        });
      }
324
    }
F
Fatih Acet 已提交
325

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

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

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

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

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

366
    onClick(item, $el, e) {
F
Fatih Acet 已提交
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
      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();
      }
    };

389
  }
F
Fatih Acet 已提交
390

391
  global.SearchAutocomplete = SearchAutocomplete;
F
Fatih Acet 已提交
392

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

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

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

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

    if ($groupOptionsDataEl.length) {
411
      gl.groupOptions = gl.groupOptions || {};
412

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

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

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

430
})(window.gl || (window.gl = {}));