labels_select.js.coffee 12.8 KB
Newer Older
1 2
class @LabelsSelect
  constructor: ->
Alfredo Sumaran's avatar
Alfredo Sumaran committed
3 4
    _this = @

5
    $('.js-label-select').each (i, dropdown) ->
Phil Hughes's avatar
Phil Hughes committed
6 7 8
      $dropdown = $(dropdown)
      projectId = $dropdown.data('project-id')
      labelUrl = $dropdown.data('labels')
9
      issueUpdateURL = $dropdown.data('issueUpdate')
Phil Hughes's avatar
Phil Hughes committed
10
      selectedLabel = $dropdown.data('selected')
11
      if selectedLabel? and not $dropdown.hasClass 'js-multiselect'
12
        selectedLabel = selectedLabel.split(',')
Alfredo Sumaran's avatar
Alfredo Sumaran committed
13 14
      newLabelField = $('#new_label_name')
      newColorField = $('#new_label_color')
Phil Hughes's avatar
Phil Hughes committed
15 16
      showNo = $dropdown.data('show-no')
      showAny = $dropdown.data('show-any')
17
      defaultLabel = $dropdown.data('default-label')
18
      abilityName = $dropdown.data('ability-name')
19 20
      $selectbox = $dropdown.closest('.selectbox')
      $block = $selectbox.closest('.block')
21
      $form = $dropdown.closest('form')
22
      $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
23
      $value = $block.find('.value')
Alfredo Sumaran's avatar
Alfredo Sumaran committed
24 25 26
      $newLabelError = $('.js-label-error')
      $colorPreview = $('.js-dropdown-label-color-preview')
      $newLabelCreateButton = $('.js-new-label-btn')
27 28

      $newLabelError.hide()
29
      $loading = $block.find('.block-loading').fadeOut()
30

31 32
      issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL?
      if issueUpdateURL
33
        labelHTMLTemplate = _.template(
34
            '<% _.each(labels, function(label){ %>
35
            <a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>">
36
            <span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;">
37
            <%- label.title %>
38 39 40
            </span>
            </a>
            <% }); %>'
41
        )
42
        labelNoneHTMLTemplate = '<span class="no-value">None</span>'
43

44
      if newLabelField.length
Alfredo Sumaran's avatar
Alfredo Sumaran committed
45 46

        # Suggested colors in the dropdown to chose from pre-chosen colors
47
        $('.suggest-colors-dropdown a').on "click", (e) ->
48 49
          e.preventDefault()
          e.stopPropagation()
50 51 52 53
          newColorField
            .val($(this).data('color'))
            .trigger('change')
          $colorPreview
Phil Hughes's avatar
Phil Hughes committed
54
            .css 'background-color', $(this).data('color')
55
            .parent()
56 57
            .addClass 'is-active'

58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
        # Cancel button takes back to first page
        resetForm = ->
          newLabelField
            .val ''
            .trigger 'change'
          newColorField
            .val ''
            .trigger 'change'
          $colorPreview
            .css 'background-color', ''
            .parent()
            .removeClass 'is-active'

        $('.dropdown-menu-back').on 'click', ->
          resetForm()

74 75 76
        $('.js-cancel-label-btn').on 'click', (e) ->
          e.preventDefault()
          e.stopPropagation()
77
          resetForm()
78 79
          $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'

80 81 82
        # Listen for change and keyup events on label and color field
        # This allows us to enable the button when ready
        enableLabelCreateButton = ->
Phil Hughes's avatar
Phil Hughes committed
83
          if newLabelField.val() isnt '' and newColorField.val() isnt ''
84
            $newLabelError.hide()
85 86 87 88
            $newLabelCreateButton.enable()
          else
            $newLabelCreateButton.disable()

89 90 91 92 93 94 95 96 97
        saveLabel = ->
          # Create new label with API
          Api.newLabel projectId, {
            name: newLabelField.val()
            color: newColorField.val()
          }, (label) ->
            $newLabelCreateButton.enable()

            if label.message?
98 99 100
              errors = _.map label.message, (value, key) ->
                "#{key} #{value[0]}"

101
              $newLabelError
102
                .html errors.join("<br/>")
103 104 105 106
                .show()
            else
              $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'

107 108 109 110 111 112 113 114 115 116
        newLabelField.on 'keyup change', enableLabelCreateButton

        newColorField.on 'keyup change', enableLabelCreateButton

        # Send the API call to create the label
        $newLabelCreateButton
          .disable()
          .on 'click', (e) ->
            e.preventDefault()
            e.stopPropagation()
117
            saveLabel()
118

119 120 121 122 123 124 125 126 127 128 129 130 131
      saveLabelData = ->
        selected = $dropdown
          .closest('.selectbox')
          .find("input[name='#{$dropdown.data('field-name')}']")
          .map(->
            @value
          ).get()
        data = {}
        data[abilityName] = {}
        data[abilityName].label_ids = selected
        if not selected.length
          data[abilityName].label_ids = ['']
        $loading.fadeIn()
132
        $dropdown.trigger('loading.gl.dropdown')
133 134
        $.ajax(
          type: 'PUT'
135
          url: issueUpdateURL
Alfredo Sumaran's avatar
Alfredo Sumaran committed
136
          dataType: 'JSON'
137 138 139
          data: data
        ).done (data) ->
          $loading.fadeOut()
140
          $dropdown.trigger('loaded.gl.dropdown')
141
          $selectbox.hide()
Jacob Schatz's avatar
Jacob Schatz committed
142
          data.issueURLSplit = issueURLSplit
143 144
          labelCount = 0
          if data.labels.length
145
            template = labelHTMLTemplate(data)
146 147
            labelCount = data.labels.length
          else
148
            template = labelNoneHTMLTemplate
149 150 151 152 153
          $value
            .removeAttr('style')
            .html(template)
          $sidebarCollapsedValue.text(labelCount)

154 155
          $('.has-tooltip', $value).tooltip(container: 'body')

156 157 158 159
          $value
            .find('a')
            .each((i) ->
              setTimeout(=>
160
                gl.animate.animate($(@), 'pulse')
161 162 163 164
              ,200 * i
              )
            )

165

Phil Hughes's avatar
Phil Hughes committed
166
      $dropdown.glDropdown(
Phil Hughes's avatar
Phil Hughes committed
167
        data: (term, callback) ->
168 169 170
          $.ajax(
            url: labelUrl
          ).done (data) ->
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
            data = _.chain data
              .groupBy (label) ->
                label.title
              .map (label) ->
                color = _.map label, (dup) ->
                  dup.color

                return {
                  id: label[0].id
                  title: label[0].title
                  color: color
                  duplicate: color.length > 1
                }
              .value()

186
            if $dropdown.hasClass 'js-extra-options'
187
              if showNo
188
                data.unshift(
189 190 191 192
                  id: 0
                  title: 'No Label'
                )

193 194 195 196 197 198 199 200
              if showAny
                data.unshift(
                  isAny: true
                  title: 'Any Label'
                )

              if data.length > 2
                data.splice 2, 0, 'divider'
201

202
            callback data
203

Alfredo Sumaran's avatar
Alfredo Sumaran committed
204
        renderRow: (label, instance) ->
Alfredo Sumaran's avatar
Alfredo Sumaran committed
205 206 207
          $li = $('<li>')
          $a  = $('<a href="#">')

Alfredo Sumaran's avatar
Alfredo Sumaran committed
208
          selectedClass = []
209
          removesAll = label.id is 0 or not label.id?
210

Alfredo Sumaran's avatar
Alfredo Sumaran committed
211
          if $dropdown.hasClass('js-filter-bulk-update')
Alfredo Sumaran's avatar
typo  
Alfredo Sumaran committed
212
            indeterminate = instance.indeterminateIds
213 214
            active = instance.activeIds

Alfredo Sumaran's avatar
typo  
Alfredo Sumaran committed
215
            if indeterminate.indexOf(label.id) isnt -1
216
              selectedClass.push 'is-indeterminate'
Alfredo Sumaran's avatar
Alfredo Sumaran committed
217

218 219 220 221 222 223 224 225 226 227
            if active.indexOf(label.id) isnt -1
              # Remove is-indeterminate class if the item will be marked as active
              i = selectedClass.indexOf 'is-indeterminate'
              selectedClass.splice i, 1 unless i is -1

              selectedClass.push 'is-active'

              # Add input manually
              instance.addInput @fieldName, label.id

228
          if $form.find("input[type='hidden']\
229
            [name='#{$dropdown.data('fieldName')}']\
230
            [value='#{this.id(label)}']").length
231 232 233 234
            selectedClass.push 'is-active'

          if $dropdown.hasClass('js-multiselect') and removesAll
            selectedClass.push 'dropdown-clear-active'
Phil Hughes's avatar
Phil Hughes committed
235

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
          if label.duplicate
            spacing = 100 / label.color.length

            # Reduce the colors to 4
            label.color = label.color.filter (color, i) ->
              i < 4

            color = _.map(label.color, (color, i) ->
              percentFirst = Math.floor(spacing * i)
              percentSecond = Math.floor(spacing * (i + 1))
              "#{color} #{percentFirst}%,#{color} #{percentSecond}% "
            ).join(',')
            color = "linear-gradient(#{color})"
          else
            if label.color?
              color = label.color[0]

          if color
            colorEl = "<span class='dropdown-label-box' style='background: #{color}'></span>"
          else
            colorEl = ''
257

258 259
          # We need to identify which items are actually labels
          if label.id
Alfredo Sumaran's avatar
Alfredo Sumaran committed
260 261
            selectedClass.push('label-item')
            $a.attr('data-label-id', label.id)
262

Alfredo Sumaran's avatar
Alfredo Sumaran committed
263
          $a.addClass(selectedClass.join(' '))
264
            .html("#{colorEl} #{label.title}")
265

Alfredo Sumaran's avatar
Alfredo Sumaran committed
266 267
          # Return generated html
          $li.html($a).prop('outerHTML')
268
        persistWhenHide: $dropdown.data('persistWhenHide')
269
        search:
270
          fields: ['title']
271
        selectable: true
272
        filterable: true
273 274 275
        toggleLabel: (selected, el) ->
          selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active')

276
          if selected and selected.title?
Arinde Eniola's avatar
Arinde Eniola committed
277
            if selected_labels.length > 1
278 279 280 281 282 283 284 285
              "#{selected.title} +#{selected_labels.length - 1} more"
            else
              selected.title
          else if not selected and selected_labels.length isnt 0
            if selected_labels.length > 1
              "#{$(selected_labels[0]).text()} +#{selected_labels.length - 1} more"
            else if selected_labels.length is 1
              $(selected_labels).text()
286 287
          else
            defaultLabel
Phil Hughes's avatar
Phil Hughes committed
288
        fieldName: $dropdown.data('field-name')
289
        id: (label) ->
290
          if $dropdown.hasClass("js-filter-submit") and not label.isAny?
291
            label.title
292 293 294 295
          else
            label.id

        hidden: ->
Jacob Schatz's avatar
Jacob Schatz committed
296 297
          page = $('body').data 'page'
          isIssueIndex = page is 'projects:issues:index'
Arinde Eniola's avatar
Arinde Eniola committed
298
          isMRIndex = page is 'projects:merge_requests:index'
299

300
          $selectbox.hide()
301 302
          # display:block overrides the hide-collapse rule
          $value.removeAttr('style')
303
          if $dropdown.hasClass 'js-multiselect'
Jacob Schatz's avatar
Jacob Schatz committed
304
            if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
305 306
              selectedLabels = $dropdown
                .closest('form')
Arinde Eniola's avatar
Arinde Eniola committed
307
                .find("input:hidden[name='#{$dropdown.data('fieldName')}']")
308
              Issuable.filterResults $dropdown.closest('form')
Jacob Schatz's avatar
Jacob Schatz committed
309 310 311
            else if $dropdown.hasClass('js-filter-submit')
              $dropdown.closest('form').submit()
            else
312 313 314 315
              if not $dropdown.hasClass 'js-filter-bulk-update'
                saveLabelData()

          if $dropdown.hasClass('js-filter-bulk-update')
316 317 318
            # If we are persisting state we need the classes
            if not @options.persistWhenHide
              $dropdown.parent().find('.is-active, .is-indeterminate').removeClass()
319

Jacob Schatz's avatar
Jacob Schatz committed
320
        multiSelect: $dropdown.hasClass 'js-multiselect'
Phil Hughes's avatar
Phil Hughes committed
321
        clicked: (label) ->
322 323
          _this.enableBulkLabelDropdown()

324
          if $dropdown.hasClass('js-filter-bulk-update')
325 326
            return

Phil Hughes's avatar
Phil Hughes committed
327 328
          page = $('body').data 'page'
          isIssueIndex = page is 'projects:issues:index'
Arinde Eniola's avatar
Arinde Eniola committed
329
          isMRIndex = page is 'projects:merge_requests:index'
Phil Hughes's avatar
Phil Hughes committed
330
          if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
331 332
            if not $dropdown.hasClass 'js-multiselect'
              selectedLabel = label.title
333
              Issuable.filterResults $dropdown.closest('form')
Phil Hughes's avatar
Phil Hughes committed
334 335
          else if $dropdown.hasClass 'js-filter-submit'
            $dropdown.closest('form').submit()
336
          else
337 338 339 340
            if $dropdown.hasClass 'js-multiselect'
              return
            else
              saveLabelData()
Alfredo Sumaran's avatar
Alfredo Sumaran committed
341

Alfredo Sumaran's avatar
typo  
Alfredo Sumaran committed
342
        setIndeterminateIds: ->
Alfredo Sumaran's avatar
Alfredo Sumaran committed
343
          if @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
Alfredo Sumaran's avatar
typo  
Alfredo Sumaran committed
344
            @indeterminateIds = _this.getIndeterminateIds()
345 346 347 348

        setActiveIds: ->
          if @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
            @activeIds = _this.getActiveIds()
349
      )
Alfredo Sumaran's avatar
Alfredo Sumaran committed
350

351 352 353 354 355 356 357 358 359 360 361 362 363 364
    @bindEvents()

  bindEvents: ->
    $('body').on 'change', '.selected_issue', @onSelectCheckboxIssue

  onSelectCheckboxIssue: ->
    return if $('.selected_issue:checked').length

    # Remove inputs
    $('.issues_bulk_update .labels-filter input[type="hidden"]').remove()

    # Also restore button text
    $('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label')

Alfredo Sumaran's avatar
typo  
Alfredo Sumaran committed
365
  getIndeterminateIds: ->
Alfredo Sumaran's avatar
Alfredo Sumaran committed
366 367 368 369 370 371 372
    label_ids = []

    $('.selected_issue:checked').each (i, el) ->
      issue_id = $(el).data('id')
      label_ids.push $("#issue_#{issue_id}").data('labels')

    _.flatten(label_ids)
373 374 375 376 377 378 379 380 381

  getActiveIds: ->
    label_ids = []

    $('.selected_issue:checked').each (i, el) ->
      issue_id = $(el).data('id')
      label_ids.push $("#issue_#{issue_id}").data('labels')

    _.intersection.apply _, label_ids
382 383 384 385

  enableBulkLabelDropdown: ->
    if $('.selected_issue:checked').length
      issuableBulkActions = $('.bulk-update').data('bulkActions')
386
      issuableBulkActions.willUpdateLabels = true