labels_select.js 17.5 KB
Newer Older
1
/* eslint-disable no-useless-return, func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread */
2 3
/* global Issuable */
/* global ListLabel */
4
import _ from 'underscore';
5 6
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
7
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
8
import DropdownUtils from './filtered_search/dropdown_utils';
9
import CreateLabelDropdown from './create_label';
10
import flash from './flash';
11

12
export default class LabelsSelect {
13
  constructor(els, options = {}) {
14 15
    var _this, $els;
    _this = this;
Phil Hughes's avatar
Phil Hughes committed
16

17
    $els = $(els);
Phil Hughes's avatar
Phil Hughes committed
18

19 20 21
    if (!els) {
      $els = $('.js-label-select');
    }
Phil Hughes's avatar
Phil Hughes committed
22

23 24 25 26 27
    $els.each(function(i, dropdown) {
      var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove, $container, $dropdownContainer;
      $dropdown = $(dropdown);
      $dropdownContainer = $dropdown.closest('.labels-filter');
      $toggleText = $dropdown.find('.dropdown-toggle-text');
Jacob Schatz's avatar
Jacob Schatz committed
28 29
      namespacePath = $dropdown.data('namespacePath');
      projectPath = $dropdown.data('projectPath');
30 31 32 33 34 35
      labelUrl = $dropdown.data('labels');
      issueUpdateURL = $dropdown.data('issueUpdate');
      selectedLabel = $dropdown.data('selected');
      if ((selectedLabel != null) && !$dropdown.hasClass('js-multiselect')) {
        selectedLabel = selectedLabel.split(',');
      }
Jacob Schatz's avatar
Jacob Schatz committed
36 37
      showNo = $dropdown.data('showNo');
      showAny = $dropdown.data('showAny');
38
      showMenuAbove = $dropdown.data('showMenuAbove');
Jacob Schatz's avatar
Jacob Schatz committed
39 40
      defaultLabel = $dropdown.data('defaultLabel');
      abilityName = $dropdown.data('abilityName');
41 42 43 44 45 46 47
      $selectbox = $dropdown.closest('.selectbox');
      $block = $selectbox.closest('.block');
      $form = $dropdown.closest('form, .js-issuable-update');
      $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span');
      $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
      $value = $block.find('.value');
      $loading = $block.find('.block-loading').fadeOut();
Jacob Schatz's avatar
Jacob Schatz committed
48
      fieldName = $dropdown.data('fieldName');
49 50 51
      useId = $dropdown.is('.js-issuable-form-dropdown, .js-filter-bulk-update, .js-label-sidebar-dropdown');
      propertyName = useId ? 'id' : 'title';
      initialSelected = $selectbox
Jacob Schatz's avatar
Jacob Schatz committed
52
        .find('input[name="' + $dropdown.data('fieldName') + '"]')
53 54 55 56 57 58 59 60 61 62
        .map(function () {
          return this.value;
        }).get();
      if (issueUpdateURL != null) {
        issueURLSplit = issueUpdateURL.split('/');
      }
      if (issueUpdateURL) {
        labelHTMLTemplate = _.template('<% _.each(labels, function(label){ %> <a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>"> <span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;"> <%- label.title %> </span> </a> <% }); %>');
        labelNoneHTMLTemplate = '<span class="no-value">None</span>';
      }
63
      const handleClick = options.handleClick;
64

65
      $sidebarLabelTooltip.tooltip();
Phil Hughes's avatar
Phil Hughes committed
66

67 68 69
      if ($dropdown.closest('.dropdown').find('.dropdown-new-label').length) {
        new CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), namespacePath, projectPath);
      }
70

71 72 73 74 75
      saveLabelData = function() {
        var data, selected;
        selected = $dropdown.closest('.selectbox').find("input[name='" + fieldName + "']").map(function() {
          return this.value;
        }).get();
76

77 78
        if (_.isEqual(initialSelected, selected)) return;
        initialSelected = selected;
79

80 81 82 83 84 85 86 87
        data = {};
        data[abilityName] = {};
        data[abilityName].label_ids = selected;
        if (!selected.length) {
          data[abilityName].label_ids = [''];
        }
        $loading.removeClass('hidden').fadeIn();
        $dropdown.trigger('loading.gl.dropdown');
88 89 90 91 92 93 94 95 96 97 98
        axios.put(issueUpdateURL, data)
          .then(({ data }) => {
            var labelCount, template, labelTooltipTitle, labelTitles;
            $loading.fadeOut();
            $dropdown.trigger('loaded.gl.dropdown');
            $selectbox.hide();
            data.issueURLSplit = issueURLSplit;
            labelCount = 0;
            if (data.labels.length) {
              template = labelHTMLTemplate(data);
              labelCount = data.labels.length;
99
            }
100 101 102 103 104
            else {
              template = labelNoneHTMLTemplate;
            }
            $value.removeAttr('style').html(template);
            $sidebarCollapsedValue.text(labelCount);
Phil Hughes's avatar
Phil Hughes committed
105

106 107 108 109
            if (data.labels.length) {
              labelTitles = data.labels.map(function(label) {
                return label.title;
              });
Phil Hughes's avatar
Phil Hughes committed
110

111 112 113 114
              if (labelTitles.length > 5) {
                labelTitles = labelTitles.slice(0, 5);
                labelTitles.push('and ' + (data.labels.length - 5) + ' more');
              }
Phil Hughes's avatar
Phil Hughes committed
115

116 117 118 119 120 121
              labelTooltipTitle = labelTitles.join(', ');
            }
            else {
              labelTooltipTitle = '';
              $sidebarLabelTooltip.tooltip('destroy');
            }
Phil Hughes's avatar
Phil Hughes committed
122

123 124 125
            $sidebarLabelTooltip
              .attr('title', labelTooltipTitle)
              .tooltip('fixTitle');
Phil Hughes's avatar
Phil Hughes committed
126

127 128 129 130 131
            $('.has-tooltip', $value).tooltip({
              container: 'body'
            });
          })
          .catch(() => flash(__('Error saving label update.')));
132 133 134 135
      };
      $dropdown.glDropdown({
        showMenuAbove: showMenuAbove,
        data: function(term, callback) {
136 137 138 139 140 141 142 143
          axios.get(labelUrl)
            .then((res) => {
              let data = _.chain(res.data).groupBy(function(label) {
                return label.title;
              }).map(function(label) {
                var color;
                color = _.map(label, function(dup) {
                  return dup.color;
Fatih Acet's avatar
Fatih Acet committed
144
                });
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
                return {
                  id: label[0].id,
                  title: label[0].title,
                  color: color,
                  duplicate: color.length > 1
                };
              }).value();
              if ($dropdown.hasClass('js-extra-options')) {
                var extraData = [];
                if (showNo) {
                  extraData.unshift({
                    id: 0,
                    title: 'No Label'
                  });
                }
                if (showAny) {
                  extraData.unshift({
                    isAny: true,
                    title: 'Any Label'
                  });
                }
                if (extraData.length) {
                  extraData.push('divider');
                  data = extraData.concat(data);
                }
Fatih Acet's avatar
Fatih Acet committed
170
              }
Phil Hughes's avatar
Phil Hughes committed
171

172 173 174
              callback(data);
              if (showMenuAbove) {
                $dropdown.data('glDropdown').positionMenuAbove();
175
              }
176 177
            })
            .catch(() => flash(__('Error fetching labels.')));
178 179 180 181 182 183 184 185 186 187 188 189 190
        },
        renderRow: function(label, instance) {
          var $a, $li, color, colorEl, indeterminate, removesAll, selectedClass, spacing, i, marked, dropdownName, dropdownValue;
          $li = $('<li>');
          $a = $('<a href="#">');
          selectedClass = [];
          removesAll = label.id <= 0 || (label.id == null);
          if ($dropdown.hasClass('js-filter-bulk-update')) {
            indeterminate = $dropdown.data('indeterminate') || [];
            marked = $dropdown.data('marked') || [];

            if (indeterminate.indexOf(label.id) !== -1) {
              selectedClass.push('is-indeterminate');
191
            }
192 193 194 195 196 197

            if (marked.indexOf(label.id) !== -1) {
              // Remove is-indeterminate class if the item will be marked as active
              i = selectedClass.indexOf('is-indeterminate');
              if (i !== -1) {
                selectedClass.splice(i, 1);
Fatih Acet's avatar
Fatih Acet committed
198
              }
199
              selectedClass.push('is-active');
Fatih Acet's avatar
Fatih Acet committed
200
            }
201 202 203 204 205 206 207 208
          } else {
            if (this.id(label)) {
              dropdownName = $dropdown.data('fieldName');
              dropdownValue = this.id(label).toString().replace(/'/g, '\\\'');

              if ($form.find("input[type='hidden'][name='" + dropdownName + "'][value='" + dropdownValue + "']").length) {
                selectedClass.push('is-active');
              }
209
            }
210 211 212

            if ($dropdown.hasClass('js-multiselect') && removesAll) {
              selectedClass.push('dropdown-clear-active');
213
            }
214 215 216 217 218 219 220
          }
          if (label.duplicate) {
            color = gl.DropdownUtils.duplicateLabelColor(label.color);
          }
          else {
            if (label.color != null) {
              color = label.color[0];
Phil Hughes's avatar
Phil Hughes committed
221
            }
222 223 224 225 226 227 228 229 230 231 232 233
          }
          if (color) {
            colorEl = "<span class='dropdown-label-box' style='background: " + color + "'></span>";
          }
          else {
            colorEl = '';
          }
          // We need to identify which items are actually labels
          if (label.id) {
            selectedClass.push('label-item');
            $a.attr('data-label-id', label.id);
          }
234
          $a.addClass(selectedClass.join(' ')).html(`${colorEl} ${_.escape(label.title)}`);
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
          // Return generated html
          return $li.html($a).prop('outerHTML');
        },
        search: {
          fields: ['title']
        },
        selectable: true,
        filterable: true,
        selected: $dropdown.data('selected') || [],
        toggleLabel: function(selected, el) {
          var isSelected = el !== null ? el.hasClass('is-active') : false;
          var title = selected.title;
          var selectedLabels = this.selected;

          if (selected.id === 0) {
            this.selected = [];
            return 'No Label';
          }
          else if (isSelected) {
            this.selected.push(title);
          }
          else {
            var index = this.selected.indexOf(title);
            this.selected.splice(index, 1);
          }

          if (selectedLabels.length === 1) {
            return selectedLabels;
          }
          else if (selectedLabels.length) {
            return selectedLabels[0] + " +" + (selectedLabels.length - 1) + " more";
          }
          else {
            return defaultLabel;
          }
        },
Jacob Schatz's avatar
Jacob Schatz committed
271
        fieldName: $dropdown.data('fieldName'),
272 273 274 275 276 277
        id: function(label) {
          if (label.id <= 0) return label.title;

          if ($dropdown.hasClass('js-issuable-form-dropdown')) {
            return label.id;
          }
Phil Hughes's avatar
Phil Hughes committed
278

279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
          if ($dropdown.hasClass("js-filter-submit") && (label.isAny == null)) {
            return label.title;
          }
          else {
            return label.id;
          }
        },
        hidden: function() {
          var isIssueIndex, isMRIndex, page, selectedLabels;
          page = $('body').attr('data-page');
          isIssueIndex = page === 'projects:issues:index';
          isMRIndex = page === 'projects:merge_requests:index';
          $selectbox.hide();
          // display:block overrides the hide-collapse rule
          $value.removeAttr('style');

          if ($dropdown.hasClass('js-issuable-form-dropdown')) {
            return;
          }

          if ($('html').hasClass('issue-boards-page')) {
            return;
          }
          if ($dropdown.hasClass('js-multiselect')) {
            if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
              selectedLabels = $dropdown.closest('form').find("input:hidden[name='" + ($dropdown.data('fieldName')) + "']");
              Issuable.filterResults($dropdown.closest('form'));
306
            }
307 308
            else if ($dropdown.hasClass('js-filter-submit')) {
              $dropdown.closest('form').submit();
309 310
            }
            else {
311 312 313
              if (!$dropdown.hasClass('js-filter-bulk-update')) {
                saveLabelData();
              }
Fatih Acet's avatar
Fatih Acet committed
314
            }
315 316 317 318
          }
        },
        multiSelect: $dropdown.hasClass('js-multiselect'),
        vue: $dropdown.hasClass('js-issue-board-sidebar'),
319 320 321
        clicked: function (clickEvent) {
          const { $el, e, isMarking } = clickEvent;
          const label = clickEvent.selectedObj;
322 323 324 325 326

          var isIssueIndex, isMRIndex, page, boardsModel;
          var fadeOutLoader = () => {
            $loading.fadeOut();
          };
327

328 329 330
          page = $('body').attr('data-page');
          isIssueIndex = page === 'projects:issues:index';
          isMRIndex = page === 'projects:merge_requests:index';
331

332 333 334 335 336
          if ($dropdown.parent().find('.is-active:not(.dropdown-clear-active)').length) {
            $dropdown.parent()
              .find('.dropdown-clear-active')
              .removeClass('is-active');
          }
337

338 339 340
          if ($dropdown.hasClass('js-issuable-form-dropdown')) {
            return;
          }
341

342 343 344 345 346
          if ($dropdown.hasClass('js-filter-bulk-update')) {
            _this.enableBulkLabelDropdown();
            _this.setDropdownData($dropdown, isMarking, label.id);
            return;
          }
347

348 349 350
          if ($dropdown.closest('.add-issues-modal').length) {
            boardsModel = gl.issueBoards.ModalStore.store.filter;
          }
351

352 353 354 355 356
          if (boardsModel) {
            if (label.isAny) {
              boardsModel['label_name'] = [];
            } else if ($el.hasClass('is-active')) {
              boardsModel['label_name'].push(label.title);
Phil Hughes's avatar
Phil Hughes committed
357 358
            }

359 360 361 362 363 364 365
            e.preventDefault();
            return;
          }
          else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
            if (!$dropdown.hasClass('js-multiselect')) {
              selectedLabel = label.title;
              return Issuable.filterResults($dropdown.closest('form'));
366
            }
367 368 369 370 371 372 373 374 375 376 377 378
          }
          else if ($dropdown.hasClass('js-filter-submit')) {
            return $dropdown.closest('form').submit();
          }
          else if ($dropdown.hasClass('js-issue-board-sidebar')) {
            if ($el.hasClass('is-active')) {
              gl.issueBoards.BoardsStore.detail.issue.labels.push(new ListLabel({
                id: label.id,
                title: label.title,
                color: label.color[0],
                textColor: '#fff'
              }));
379
            }
380 381 382 383 384 385
            else {
              var labels = gl.issueBoards.BoardsStore.detail.issue.labels;
              labels = labels.filter(function (selectedLabel) {
                return selectedLabel.id !== label.id;
              });
              gl.issueBoards.BoardsStore.detail.issue.labels = labels;
386
            }
387

388 389 390 391 392 393
            $loading.fadeIn();

            gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
              .then(fadeOutLoader)
              .catch(fadeOutLoader);
          }
Simon Knox's avatar
Simon Knox committed
394 395 396 397
          else if (handleClick) {
            e.preventDefault();
            handleClick(label);
          }
398 399
          else {
            if ($dropdown.hasClass('js-multiselect')) {
400

401
            }
402
            else {
403
              return saveLabelData();
Fatih Acet's avatar
Fatih Acet committed
404
            }
405 406
          }
        },
Fatih Acet's avatar
Fatih Acet committed
407 408
      });

409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
      // Set dropdown data
      _this.setOriginalDropdownData($dropdownContainer, $dropdown);
    });
    this.bindEvents();
  }

  bindEvents() {
    return $('body').on('change', '.selected_issue', this.onSelectCheckboxIssue);
  }
  // eslint-disable-next-line class-methods-use-this
  onSelectCheckboxIssue() {
    if ($('.selected_issue:checked').length) {
      return;
    }
    return $('.issues-bulk-update .labels-filter .dropdown-toggle-text').text('Label');
  }
  // eslint-disable-next-line class-methods-use-this
  enableBulkLabelDropdown() {
    IssuableBulkUpdateActions.willUpdateLabels = true;
  }
  // eslint-disable-next-line class-methods-use-this
  setDropdownData($dropdown, isMarking, value) {
    var i, markedIds, unmarkedIds, indeterminateIds;

    markedIds = $dropdown.data('marked') || [];
    unmarkedIds = $dropdown.data('unmarked') || [];
    indeterminateIds = $dropdown.data('indeterminate') || [];

    if (isMarking) {
      markedIds.push(value);

      i = indeterminateIds.indexOf(value);
      if (i > -1) {
        indeterminateIds.splice(i, 1);
Fatih Acet's avatar
Fatih Acet committed
443 444
      }

445 446 447 448 449 450 451 452 453
      i = unmarkedIds.indexOf(value);
      if (i > -1) {
        unmarkedIds.splice(i, 1);
      }
    } else {
      // If marked item (not common) is unmarked
      i = markedIds.indexOf(value);
      if (i > -1) {
        markedIds.splice(i, 1);
454
      }
Fatih Acet's avatar
Fatih Acet committed
455

456 457 458 459
      // If an indeterminate item is being unmarked
      if (IssuableBulkUpdateActions.getOriginalIndeterminateIds().indexOf(value) > -1) {
        unmarkedIds.push(value);
      }
460

461 462 463 464 465 466
      // If a marked item is being unmarked
      // (a marked item could also be a label that is present in all selection)
      if (IssuableBulkUpdateActions.getOriginalCommonIds().indexOf(value) > -1) {
        unmarkedIds.push(value);
      }
    }
467

468 469 470 471 472 473 474 475 476 477 478 479 480
    $dropdown.data('marked', markedIds);
    $dropdown.data('unmarked', unmarkedIds);
    $dropdown.data('indeterminate', indeterminateIds);
  }
  // eslint-disable-next-line class-methods-use-this
  setOriginalDropdownData($container, $dropdown) {
    const labels = [];
    $container.find('[name="label_name[]"]').map(function() {
      return labels.push(this.value);
    });
    $dropdown.data('marked', labels);
  }
}