Skip to content

Commit

Permalink
Fixes #3900 - Be able to select more than one owner or organization i…
Browse files Browse the repository at this point in the history
…n condition for overviews/triggers/schedulers like you can do it for state, priority or group
  • Loading branch information
Bola Ahmed Buari authored and rolfschmidt committed Jan 12, 2022
1 parent 50e3b98 commit bf3067d
Show file tree
Hide file tree
Showing 11 changed files with 746 additions and 91 deletions.
Expand Up @@ -3,7 +3,7 @@ class App.UiElement.autocompletion_ajax
@render: (attribute, params = {}, form) ->
if params[attribute.name] || attribute.value
object = App[attribute.relation].find(params[attribute.name] || attribute.value)
valueName = object.displayName()
valueName = object.displayName() if object

# selectable search
searchableAjaxSelectObject = new App.SearchableAjaxSelect(
Expand All @@ -17,5 +17,6 @@ class App.UiElement.autocompletion_ajax
limit: 40
object: attribute.relation
ajax: true
multiple: attribute.multiple
)
searchableAjaxSelectObject.element()
@@ -0,0 +1,6 @@
# coffeelint: disable=camel_case_classes
class App.UiElement.autocompletion_ajax_search extends App.UiElement.autocompletion_ajax
@render: (attributeOrig, params = {}, form) ->
attribute = _.clone(attributeOrig)
attribute.multiple = true
super(attribute, params = {}, form)
Expand Up @@ -3,4 +3,5 @@ class App.UiElement.user_autocompletion_search
@render: (attributeOrig, params = {}) ->
attribute = _.clone(attributeOrig)
attribute.disableCreateObject = true
attribute.multiple = true
new App.UserOrganizationAutocompletion(attribute: attribute, params: params).element()
2 changes: 1 addition & 1 deletion app/assets/javascripts/app/controllers/overview.coffee
Expand Up @@ -27,7 +27,7 @@ class Overview extends App.ControllerSubContent
{ name: __('New Overview'), 'data-type': 'new', class: 'btn--success' }
]
container: @el.closest('.content')
large: true
veryLarge: true
dndCallback: (e, item) =>
items = @el.find('table > tbody > tr')
prios = []
Expand Down
Expand Up @@ -80,10 +80,9 @@ class App.ObjectOrganizationAutocompletion extends App.Controller

onBlur: =>
selectObject = @objectSelect.val()
if _.isEmpty(selectObject)
if _.isEmpty(selectObject) && !@attribute.multiple
@objectId.val('')
return
if @attribute.guess is true
else if @attribute.guess is true
currentObjectId = @objectId.val()
if _.isEmpty(currentObjectId) || currentObjectId.match(/^guess:/)
if !_.isEmpty(selectObject)
Expand All @@ -95,31 +94,28 @@ class App.ObjectOrganizationAutocompletion extends App.Controller

onObjectClick: (e) =>
objectId = $(e.currentTarget).data('object-id')
@selectObject(objectId)
objectName = $(e.currentTarget).find('.recipientList-name').text().trim()
@selectObject(objectId, objectName)
@close()

selectObject: (objectId) =>
if @attribute.multiple and @objectId.val()
# add objectId to end of comma separated list
objectId = _.chain( @objectId.val().split(',') ).push(objectId).join(',').value()

@objectSelect.val('')
@objectId.val(objectId).trigger('change')
selectObject: (objectId, objectName) =>
if @attribute.multiple
@addValueToObjectInput(objectName, objectId)
else
@objectSelect.val('')
@objectId.val(objectId).trigger('change')

executeCallback: =>
# with @attribute.multiple this can be several objects ids.
# Only work with the last one since its the newest one
objectId = @objectId.val().split(',').pop()
if @attribute.multiple
# create token
@createToken(@currentObject) if @currentObject
@currentObject = null

if objectId && App[@objectSingle].exists(objectId)
object = App[@objectSingle].find(objectId)
name = object.displayName()

if @attribute.multiple

# create token
@createToken(name, objectId)
else
else
objectId = @objectId.val()
if objectId && App[@objectSingle].exists(objectId)
object = App[@objectSingle].find(objectId)
name = object.displayName()
if object.email

# quote name for special character
Expand All @@ -132,10 +128,10 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
if @callback
@callback(objectId)

createToken: (name, objectId) =>
createToken: ({name, value}) =>
@objectSelect.before App.view('generic/token')(
name: name
value: objectId
value: value
)

removeThisToken: (e) =>
Expand All @@ -149,12 +145,9 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
else
token = which

# remove objectId from input
index = @$('.token').index(token)
ids = @objectId.val().split(',')
ids.splice(index, 1)
@objectId.val ids.join(',')

id = token.data('value')
@objectId.find("[value=#{id}]").remove()
@objectId.trigger('change')
token.remove()

navigateByKeyboard: (e) =>
Expand All @@ -170,7 +163,7 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
@objectSelect.val('').trigger('change')
# remove last token on backspace
when 8
if @objectSelect.val() is ''
if @objectSelect.val() is '' && @objectSelect.is(e.target)
@removeToken('last')
# close on tab
when 9 then @close()
Expand Down Expand Up @@ -223,7 +216,8 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
return
objectId = recipientListOrganizationMembers.find('li.is-active').data('object-id')
return if !objectId
@selectObject(objectId)
objectName = recipientListOrganizationMembers.find('li.is-active .recipientList-name').text().trim()
@selectObject(objectId, objectName)
@close() if !@attribute.multiple
return

Expand All @@ -233,7 +227,8 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
if objectId is 'new'
@newObject()
else
@selectObject(objectId)
objectName = @recipientList.find('li.is-active .recipientList-name').text().trim()
@selectObject(objectId, objectName)
@close() if !@attribute.multiple
return

Expand All @@ -242,6 +237,14 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
@showOrganizationMembers(undefined, @recipientList.find('li.is-active'))


addValueToObjectInput: (objectName, objectId) ->
@objectSelect.val('')
@currentObject = {name: objectName, value: objectId}
if @objectId.val()
return if @objectId.val().includes("#{objectId}") # cast objectId to string before check
@objectId.append("<option value=#{App.Utils.htmlEscape(@currentObject.value)} selected>#{App.Utils.htmlEscape(@currentObject.name)}</option>")
@objectId.trigger('change')

buildOrganizationItem: (organization) ->
objectCount = 0
if organization[@referenceAttribute]
Expand Down Expand Up @@ -315,17 +318,23 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
# fallback for if the value is not an array
if typeof @attribute.value isnt 'object'
@attribute.value = [@attribute.value]
value = @attribute.value.join ','

# create tokens
# create tokens and attribute values
values = []
for objectId in @attribute.value
if App[@objectSingle].exists objectId
objectName = App[@objectSingle].find(objectId).displayName()
objectValue = objectId
values.push({name: objectName, value: objectValue})
tokens += App.view('generic/token')(
name: App[@objectSingle].find(objectId).displayName()
value: objectId
name: objectName
value: objectValue
)
else
@log 'objectId doesn\'t exist', objectId

@attribute.value = values

else
value = @attribute.value
if value
Expand Down Expand Up @@ -369,7 +378,7 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
@recipientList.append(@buildObjectNew())

# reset object selection
@resetObjectSelection()
@resetObjectSelection() if !@attribute.multiple
return

# show dropdown
Expand Down
99 changes: 86 additions & 13 deletions app/assets/javascripts/app/lib/app_post/searchable_select.coffee
Expand Up @@ -15,6 +15,7 @@ class App.SearchableSelect extends Spine.Controller
'shown.bs.dropdown': 'onDropdownShown'
'hidden.bs.dropdown': 'onDropdownHidden'
'keyup .js-input': 'onKeyUp'
'click .js-remove': 'removeThisToken'

elements:
'.js-dropdown': 'dropdown'
Expand All @@ -38,10 +39,33 @@ class App.SearchableSelect extends Spine.Controller
render: ->
@updateAttributeValueName()

tokens = ''
if @attribute.multiple && @attribute.value
object = @attribute.object

# fallback for if the value is not an array
if typeof @attribute.value isnt 'object'
@attribute.value = [@attribute.value]

# create tokens and attribute values
values = []
for dataId in @attribute.value
if App[object].exists dataId
name = App[object].find(dataId).displayName()
value = dataId
values.push({name: name, value: value})
tokens += App.view('generic/token')(
name: name
value: value
)

@attribute.value = values

@html App.view('generic/searchable_select')
attribute: @attribute
options: @renderAllOptions('', @attribute.options, 0)
submenus: @renderSubmenus(@attribute.options)
tokens: tokens

# initial data
@currentMenu = @findMenuContainingValue(@attribute.value)
Expand Down Expand Up @@ -133,12 +157,12 @@ class App.SearchableSelect extends Spine.Controller
@unhighlightCurrentItem()
@isOpen = false

if !@input.val()
if !@input.val() && !@attribute.multiple
@updateAttributeValueName()
@input.val(@attribute.valueName)

onKeyUp: =>
return if @input.val().trim() isnt ''
return if @input.val().trim() isnt '' || @attribute.multiple
@shadowInput.val('')

toggle: =>
Expand All @@ -157,6 +181,9 @@ class App.SearchableSelect extends Spine.Controller
when 13 then @onEnter(event)
when 27 then @onEscape(event)
when 9 then @onTab(event)
when 8 # remove last token on backspace
if @input.val() is '' && @input.is(event.target) && @attribute.multiple
@removeToken('last')

onEscape: ->
if @isOpen
Expand Down Expand Up @@ -192,7 +219,7 @@ class App.SearchableSelect extends Spine.Controller
@clearAutocomplete()

autocompleteOrNavigateIn: (event) ->
if @currentItem.hasClass('js-enter')
if @currentItem && @currentItem.hasClass('js-enter')
@navigateIn(event)
else
@fillWithAutocompleteSuggestion(event)
Expand All @@ -215,8 +242,11 @@ class App.SearchableSelect extends Spine.Controller
# current position
caretPosition = @invisiblePart.text().length + 1

@input.val(@suggestion)
@shadowInput.val(@suggestionValue)
if @attribute.multiple
@addValueToShadowInput(@suggestion, @suggestionValue)
else
@input.val(@suggestion)
@shadowInput.val(@suggestionValue)
@clearAutocomplete()
@toggle()

Expand All @@ -242,10 +272,15 @@ class App.SearchableSelect extends Spine.Controller
selectItem: (event) ->
currentText = event.currentTarget.querySelector('span.searchableSelect-option-text').textContent.trim()
return if !currentText
@input.val currentText
@input.trigger('change')
@shadowInput.val event.currentTarget.getAttribute('data-value')
@shadowInput.trigger('change')

dataId = event.currentTarget.getAttribute('data-value')
if @attribute.multiple
@addValueToShadowInput(currentText, dataId)
else
@input.val currentText
@input.trigger('change')
@shadowInput.val dataId
@shadowInput.trigger('change')

navigateIn: (event) ->
event.stopPropagation()
Expand Down Expand Up @@ -354,11 +389,14 @@ class App.SearchableSelect extends Spine.Controller
if @currentItem || !@attribute.unknown
valueName = @currentItem.children('span.searchableSelect-option-text').text().trim()
value = @currentItem.attr('data-value')
@input.val valueName
@shadowInput.val value
if @attribute.multiple
@addValueToShadowInput(valueName, value)
else
@input.val valueName
@shadowInput.val value
@shadowInput.trigger('change')

@input.trigger('change')
@shadowInput.trigger('change')

if @currentItem
if @currentItem.hasClass('js-enter')
Expand Down Expand Up @@ -386,17 +424,44 @@ class App.SearchableSelect extends Spine.Controller
onShadowChange: ->
value = @shadowInput.val()

if @attribute.multiple and @currentData
# create token
@createToken(@currentData)
@currentData = null

if Array.isArray(@attribute.options)
for option in @attribute.options
option.selected = (option.value + '') == value # makes sure option value is always a string

createToken: ({name, value}) =>
@input.before App.view('generic/token')(
name: name
value: value
)

removeThisToken: (e) =>
@removeToken $(e.currentTarget).parents('.token')

removeToken: (which) =>
switch which
when 'last'
token = @$('.token').last()
return if not token.size()
else
token = which

id = token.data('value')
@shadowInput.find("[value=#{id}]").remove()
@shadowInput.trigger('change')
token.remove()

onInput: (event) =>
@toggle() if not @isOpen

@query = @input.val()
@filterByQuery @query

if @attribute.unknown
if @attribute.unknown && !@attribute.multiple
@shadowInput.val @query

filterByQuery: (query) ->
Expand All @@ -422,6 +487,14 @@ class App.SearchableSelect extends Spine.Controller
else
@highlightFirst(true)

addValueToShadowInput: (currentText, dataId) ->
@input.val('')
@currentData = {name: currentText, value: dataId}
if @shadowInput.val()
return if @shadowInput.val().includes("#{dataId}") # cast dataId to string before check
@shadowInput.append($('<option/>').attr('selected', true).attr('value', @currentData.value).text(@currentData.name))
@shadowInput.trigger('change')

highlightFirst: (autocomplete) ->
@unhighlightCurrentItem()
@currentItem = @getCurrentOptions().not('.is-hidden').first()
Expand Down

0 comments on commit bf3067d

Please sign in to comment.