|
@@ -0,0 +1,431 @@
|
|
|
+class App.ObjectOrganizationAutocompletion extends App.Controller
|
|
|
+ className: 'dropdown js-recipientDropdown'
|
|
|
+ events:
|
|
|
+ 'hide.bs.dropdown .js-recipientDropdown': 'hideOrganizationMembers'
|
|
|
+ 'click .js-organization': 'showOrganizationMembers'
|
|
|
+ 'click .js-back': 'hideOrganizationMembers'
|
|
|
+ 'click .js-object': 'onObjectClick'
|
|
|
+ 'click .js-objectNew': 'newObject'
|
|
|
+ 'focus .js-objectSelect': 'onFocus'
|
|
|
+ 'click .js-objectSelect': 'stopPropagation'
|
|
|
+ 'blur .js-objectSelect': 'onBlur'
|
|
|
+ 'click .form-control': 'focusInput'
|
|
|
+ 'click': 'stopPropagation'
|
|
|
+ 'change .js-objectId': 'executeCallback'
|
|
|
+ 'click .js-remove': 'removeThisToken'
|
|
|
+
|
|
|
+ elements:
|
|
|
+ '.recipientList': 'recipientList'
|
|
|
+ '.js-objectSelect': 'objectSelect'
|
|
|
+ '.js-objectId': 'objectId'
|
|
|
+ '.form-control': 'formControl'
|
|
|
+
|
|
|
+ templateObjectItem: 'generic/object_search/item_object'
|
|
|
+ templateObjectNew: 'generic/object_search/new_object'
|
|
|
+ templateOrganizationItem: 'generic/object_search/item_organization'
|
|
|
+ templateOrganizationItemMembers: 'generic/object_search/item_organization_members'
|
|
|
+
|
|
|
+ objectSingle: 'User'
|
|
|
+ objectIcon: 'user'
|
|
|
+ objectSingels: 'People'
|
|
|
+ objectCreate: 'Create new object'
|
|
|
+ referenceAttribute: 'member_ids'
|
|
|
+
|
|
|
+ constructor: (params) ->
|
|
|
+ super
|
|
|
+
|
|
|
+ @lazySearch = _.debounce(@searchObject, 200)
|
|
|
+
|
|
|
+ @key = Math.floor( Math.random() * 999999 ).toString()
|
|
|
+
|
|
|
+ if !@attribute.source
|
|
|
+ @attribute.source = "#{@apiPath}/search/user-organization"
|
|
|
+ @build()
|
|
|
+
|
|
|
+ # set current value
|
|
|
+ if @attribute.value and @callback
|
|
|
+ @callback(@attribute.value)
|
|
|
+
|
|
|
+ element: =>
|
|
|
+ @el
|
|
|
+
|
|
|
+ release: ->
|
|
|
+ $(window).off 'click.ObjectOrganizationAutocompletion'
|
|
|
+
|
|
|
+ open: =>
|
|
|
+ # prevent rebinding of keydown event
|
|
|
+ return if @el.hasClass 'open'
|
|
|
+
|
|
|
+ @el.addClass('open')
|
|
|
+ $(window).on 'click.ObjectOrganizationAutocompletion', @close
|
|
|
+ $(window).on 'keydown.ObjectOrganizationAutocompletion', @navigateByKeyboard
|
|
|
+
|
|
|
+ close: =>
|
|
|
+ $(window).off 'keydown.ObjectOrganizationAutocompletion'
|
|
|
+ @el.removeClass('open')
|
|
|
+
|
|
|
+ $(window).off 'click.ObjectOrganizationAutocompletion'
|
|
|
+
|
|
|
+ onFocus: =>
|
|
|
+ @formControl.addClass 'focus'
|
|
|
+ @open()
|
|
|
+
|
|
|
+ focusInput: =>
|
|
|
+ @objectSelect.focus() if not @formControl.hasClass 'focus'
|
|
|
+
|
|
|
+ onBlur: =>
|
|
|
+ selectObject = @objectSelect.val()
|
|
|
+ if _.isEmpty(selectObject)
|
|
|
+ @objectId.val('')
|
|
|
+ return
|
|
|
+ if @attribute.guess is true
|
|
|
+ currentObjectId = @objectId.val()
|
|
|
+ if _.isEmpty(currentObjectId) || currentObjectId.match(/^guess:/)
|
|
|
+ if !_.isEmpty(selectObject)
|
|
|
+ @objectId.val("guess:#{selectObject}")
|
|
|
+ @formControl.removeClass 'focus'
|
|
|
+
|
|
|
+ onObjectClick: (e) =>
|
|
|
+ objectId = $(e.currentTarget).data('object-id')
|
|
|
+ @selectObject(objectId)
|
|
|
+ @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')
|
|
|
+
|
|
|
+ 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()
|
|
|
+
|
|
|
+ return if !objectId
|
|
|
+ return if !App[@objectSingle].exists(objectId)
|
|
|
+ object = App[@objectSingle].find(objectId)
|
|
|
+ name = object.displayName()
|
|
|
+
|
|
|
+ if @attribute.multiple
|
|
|
+ # create token
|
|
|
+ @createToken name, objectId
|
|
|
+ else
|
|
|
+ if object.email
|
|
|
+ name += " <#{object.email}>"
|
|
|
+
|
|
|
+ @objectSelect.val(name)
|
|
|
+
|
|
|
+ if @callback
|
|
|
+ @callback(objectId)
|
|
|
+
|
|
|
+ createToken: (name, objectId) =>
|
|
|
+ @objectSelect.before App.view('generic/token')(
|
|
|
+ name: name
|
|
|
+ value: objectId
|
|
|
+ )
|
|
|
+
|
|
|
+ removeThisToken: (e) =>
|
|
|
+ @removeToken $(e.currentTarget).parents('.token')
|
|
|
+
|
|
|
+ removeToken: (which) =>
|
|
|
+ switch which
|
|
|
+ when 'last'
|
|
|
+ token = @$('.token').last()
|
|
|
+ return if not token.size()
|
|
|
+ else
|
|
|
+ token = which
|
|
|
+
|
|
|
+ # remove objectId from input
|
|
|
+ index = @$('.token').index(token)
|
|
|
+ ids = @objectId.val().split(',')
|
|
|
+ ids.splice(index, 1)
|
|
|
+ @objectId.val ids.join(',')
|
|
|
+
|
|
|
+ token.remove()
|
|
|
+
|
|
|
+ navigateByKeyboard: (e) =>
|
|
|
+ switch e.keyCode
|
|
|
+ # clean input on esc
|
|
|
+ when 27
|
|
|
+ # if org member selection is shown, go back to member list
|
|
|
+ if !@recipientList.hasClass('is-shown')
|
|
|
+ @hideOrganizationMembers()
|
|
|
+ return
|
|
|
+
|
|
|
+ # empty object selection and close
|
|
|
+ @objectSelect.val('').trigger('change')
|
|
|
+ # remove last token on backspace
|
|
|
+ when 8
|
|
|
+ if @objectSelect.val() is ''
|
|
|
+ @removeToken('last')
|
|
|
+ # close on tab
|
|
|
+ when 9 then @close()
|
|
|
+ # ignore left and right
|
|
|
+ when 37, 39 then return
|
|
|
+ # up / select upper item
|
|
|
+ when 38
|
|
|
+ e.preventDefault()
|
|
|
+ if @recipientList.hasClass('is-shown')
|
|
|
+ if @recipientList.find('li.is-active').length is 0
|
|
|
+ @recipientList.find('li').last().addClass('is-active')
|
|
|
+ else
|
|
|
+ if @recipientList.find('li.is-active').prev().length isnt 0
|
|
|
+ @recipientList.find('li.is-active').removeClass('is-active').prev().addClass('is-active')
|
|
|
+ return
|
|
|
+ recipientListOrgMemeber = @$('.recipientList-organizationMembers').not('.hide')
|
|
|
+ if recipientListOrgMemeber.not('.hide').find('li.is-active').length is 0
|
|
|
+ recipientListOrgMemeber.not('.hide').find('li').last().addClass('is-active')
|
|
|
+ else
|
|
|
+ if recipientListOrgMemeber.not('.hide').find('li.is-active').prev().length isnt 0
|
|
|
+ recipientListOrgMemeber.not('.hide').find('li.is-active').removeClass('is-active').prev().addClass('is-active')
|
|
|
+ return
|
|
|
+ # down / select lower item
|
|
|
+ when 40
|
|
|
+ e.preventDefault()
|
|
|
+ if @recipientList.hasClass('is-shown')
|
|
|
+ if @recipientList.find('li.is-active').length is 0
|
|
|
+ @recipientList.find('li').first().addClass('is-active')
|
|
|
+ else
|
|
|
+ if @recipientList.find('li.is-active').next().length isnt 0
|
|
|
+ @recipientList.find('li.is-active').removeClass('is-active').next().addClass('is-active')
|
|
|
+ return
|
|
|
+ recipientListOrgMemeber = @$('.recipientList-organizationMembers').not('.hide')
|
|
|
+ if recipientListOrgMemeber.not('.hide').find('li.is-active').length is 0
|
|
|
+ recipientListOrgMemeber.find('li').first().addClass('is-active')
|
|
|
+ else
|
|
|
+ if recipientListOrgMemeber.not('.hide').find('li.is-active').next().length isnt 0
|
|
|
+ recipientListOrgMemeber.not('.hide').find('li.is-active').removeClass('is-active').next().addClass('is-active')
|
|
|
+ return
|
|
|
+ # enter / take item
|
|
|
+ when 13
|
|
|
+ e.preventDefault()
|
|
|
+ e.stopPropagation()
|
|
|
+
|
|
|
+ # nav by org member selection
|
|
|
+ if !@recipientList.hasClass('is-shown')
|
|
|
+ recipientListOrganizationMembers = @$('.recipientList-organizationMembers').not('.hide')
|
|
|
+ if recipientListOrganizationMembers.find('.js-back.is-active').get(0)
|
|
|
+ @hideOrganizationMembers()
|
|
|
+ return
|
|
|
+ objectId = recipientListOrganizationMembers.find('li.is-active').data('object-id')
|
|
|
+ return if !objectId
|
|
|
+ @selectObject(objectId)
|
|
|
+ @close() if !@attribute.multiple
|
|
|
+ return
|
|
|
+
|
|
|
+ # nav by object list selection
|
|
|
+ objectId = @recipientList.find('li.is-active').data('object-id')
|
|
|
+ if objectId
|
|
|
+ if objectId is 'new'
|
|
|
+ @newObject()
|
|
|
+ else
|
|
|
+ @selectObject(objectId)
|
|
|
+ @close() if !@attribute.multiple
|
|
|
+ return
|
|
|
+
|
|
|
+ organizationId = @recipientList.find('li.is-active').data('organization-id')
|
|
|
+ return if !organizationId
|
|
|
+ @showOrganizationMembers(undefined, @recipientList.find('li.is-active'))
|
|
|
+
|
|
|
+
|
|
|
+ buildOrganizationItem: (organization) ->
|
|
|
+ objectCount = 0
|
|
|
+ if organization[@referenceAttribute]
|
|
|
+ objectCount = organization[@referenceAttribute].length
|
|
|
+ App.view(@templateOrganizationItem)(
|
|
|
+ organization: organization
|
|
|
+ objectSingels: @objectSingels
|
|
|
+ objectCount: objectCount
|
|
|
+ )
|
|
|
+
|
|
|
+ buildOrganizationMembers: (organization) =>
|
|
|
+ organizationMemebers = $( App.view(@templateOrganizationItemMembers)(
|
|
|
+ organization: organization
|
|
|
+ ) )
|
|
|
+ if organization[@referenceAttribute]
|
|
|
+ for objectId in organization[@referenceAttribute]
|
|
|
+ object = App[@objectSingle].fullLocal(objectId)
|
|
|
+ organizationMemebers.append(@buildObjectItem(object))
|
|
|
+
|
|
|
+ buildObjectItem: (object) =>
|
|
|
+ App.view(@templateObjectItem)(
|
|
|
+ object: object
|
|
|
+ icon: @objectIcon
|
|
|
+ )
|
|
|
+
|
|
|
+ buildObjectNew: =>
|
|
|
+ App.view(@templateObjectNew)(
|
|
|
+ objectCreate: @objectCreate
|
|
|
+ )
|
|
|
+
|
|
|
+ build: =>
|
|
|
+ tokens = ''
|
|
|
+ name = ''
|
|
|
+ value = ''
|
|
|
+
|
|
|
+ if @attribute.multiple && @attribute.value
|
|
|
+
|
|
|
+ # 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
|
|
|
+ for objectId in @attribute.value
|
|
|
+ if App[@objectSingle].exists objectId
|
|
|
+ tokens += App.view('generic/token')(
|
|
|
+ name: App[@objectSingle].find(objectId).displayName()
|
|
|
+ value: objectId
|
|
|
+ )
|
|
|
+ else
|
|
|
+ @log 'objectId doesn\'t exist', objectId
|
|
|
+ else
|
|
|
+ value = @attribute.value
|
|
|
+ if value
|
|
|
+ if App[@objectSingle].exists(value)
|
|
|
+ object = App[@objectSingle].find(value)
|
|
|
+ name = object.displayName()
|
|
|
+ if object.email
|
|
|
+ name += " <#{object.email}>"
|
|
|
+ else if @params && @params["#{@attribute.name}_completion"]
|
|
|
+ name = @params["#{@attribute.name}_completion"]
|
|
|
+ else
|
|
|
+ @log 'objectId doesn\'t exist', value
|
|
|
+
|
|
|
+ @html App.view('generic/object_search/input')(
|
|
|
+ attribute: @attribute
|
|
|
+ value: value
|
|
|
+ tokens: tokens
|
|
|
+ name: name
|
|
|
+ )
|
|
|
+
|
|
|
+ if !@attribute.disableCreateObject
|
|
|
+ @recipientList.append(@buildObjectNew())
|
|
|
+
|
|
|
+ # start search
|
|
|
+ @searchTerm = ''
|
|
|
+
|
|
|
+ @objectSelect.on 'keyup', @onKeyUp
|
|
|
+
|
|
|
+ onKeyUp: (e) =>
|
|
|
+ query = $(e.target).val().trim()
|
|
|
+ return if @searchTerm is query
|
|
|
+ @searchTerm = query
|
|
|
+
|
|
|
+ @hideOrganizationMembers()
|
|
|
+
|
|
|
+ # hide dropdown
|
|
|
+ if !query
|
|
|
+ @emptyResultList()
|
|
|
+
|
|
|
+ if !@attribute.disableCreateObject
|
|
|
+ @recipientList.append(@buildObjectNew())
|
|
|
+
|
|
|
+ # show dropdown
|
|
|
+ if query && ( !@attribute.minLengt || @attribute.minLengt <= query.length )
|
|
|
+ @lazySearch(query)
|
|
|
+
|
|
|
+ searchObject: (query) =>
|
|
|
+ @ajax(
|
|
|
+ id: "searchObject#{@key}"
|
|
|
+ type: 'GET'
|
|
|
+ url: @attribute.source
|
|
|
+ data:
|
|
|
+ query: query
|
|
|
+ processData: true
|
|
|
+ success: (data, status, xhr) =>
|
|
|
+ @emptyResultList()
|
|
|
+
|
|
|
+ # load assets
|
|
|
+ App.Collection.loadAssets(data.assets)
|
|
|
+
|
|
|
+ # build markup
|
|
|
+ for item in data.result
|
|
|
+
|
|
|
+ # organization
|
|
|
+ if item.type is 'Organization'
|
|
|
+ organization = App.Organization.fullLocal(item.id)
|
|
|
+ @recipientList.append(@buildOrganizationItem(organization))
|
|
|
+
|
|
|
+ # objectss of organization
|
|
|
+ if organization[@referenceAttribute]
|
|
|
+ @$('.dropdown-menu').append(@buildOrganizationMembers(organization))
|
|
|
+
|
|
|
+ # objectss
|
|
|
+ if item.type is @objectSingle
|
|
|
+ object = App[@objectSingle].fullLocal(item.id)
|
|
|
+ @recipientList.append(@buildObjectItem(object))
|
|
|
+
|
|
|
+ if !@attribute.disableCreateObject
|
|
|
+ @recipientList.append(@buildObjectNew())
|
|
|
+
|
|
|
+ @recipientList.find('.js-object').first().addClass('is-active')
|
|
|
+ )
|
|
|
+
|
|
|
+ emptyResultList: =>
|
|
|
+ @recipientList.empty()
|
|
|
+ @$('.recipientList-organizationMembers').remove()
|
|
|
+
|
|
|
+ showOrganizationMembers: (e,listEntry) =>
|
|
|
+ if e
|
|
|
+ e.stopPropagation()
|
|
|
+ listEntry = $(e.currentTarget)
|
|
|
+
|
|
|
+ organizationId = listEntry.data('organization-id')
|
|
|
+
|
|
|
+ @organizationList = @$("[organization-id=#{ organizationId }]")
|
|
|
+
|
|
|
+ return if !@organizationList.get(0)
|
|
|
+
|
|
|
+ @recipientList.removeClass('is-shown')
|
|
|
+ @$('.recipientList-organizationMembers').addClass('is-shown')
|
|
|
+
|
|
|
+ # move organization-list to the right and slide it in
|
|
|
+ $.Velocity.hook(@organizationList, 'translateX', '100%')
|
|
|
+ @organizationList.removeClass('hide')
|
|
|
+
|
|
|
+ @organizationList.velocity
|
|
|
+ properties:
|
|
|
+ translateX: 0
|
|
|
+ options:
|
|
|
+ speed: 300
|
|
|
+
|
|
|
+ # fade out list
|
|
|
+ @recipientList.velocity
|
|
|
+ properties:
|
|
|
+ translateX: '-100%'
|
|
|
+ options:
|
|
|
+ speed: 300
|
|
|
+ complete: => @recipientList.height(@organizationList.height())
|
|
|
+
|
|
|
+ hideOrganizationMembers: (e) =>
|
|
|
+ e && e.stopPropagation()
|
|
|
+
|
|
|
+ @recipientList.addClass('is-shown')
|
|
|
+ @$('.recipientList-organizationMembers').removeClass('is-shown')
|
|
|
+
|
|
|
+ return if !@organizationList
|
|
|
+
|
|
|
+ # fade list back in
|
|
|
+ @recipientList.velocity
|
|
|
+ properties:
|
|
|
+ translateX: 0
|
|
|
+ options:
|
|
|
+ speed: 300
|
|
|
+
|
|
|
+ # reset list height
|
|
|
+ @recipientList.height('')
|
|
|
+
|
|
|
+ # slide out organization-list and hide it
|
|
|
+ @organizationList.velocity
|
|
|
+ properties:
|
|
|
+ translateX: '100%'
|
|
|
+ options:
|
|
|
+ speed: 300
|
|
|
+ complete: => @organizationList.addClass('hide')
|
|
|
+
|
|
|
+ newObject: (e) ->
|
|
|
+ if e
|
|
|
+ e.preventDefault()
|