diff --git a/app/assets/javascripts/app/controllers/layout_ref.coffee b/app/assets/javascripts/app/controllers/layout_ref.coffee index 7b7cd8435..8813525e1 100644 --- a/app/assets/javascripts/app/controllers/layout_ref.coffee +++ b/app/assets/javascripts/app/controllers/layout_ref.coffee @@ -1488,6 +1488,22 @@ class InputsRef extends App.ControllerContent @$('.searchableAjaxSelectPlaceholder').replaceWith( searchableAjaxSelectObject.element() ) + # user organization autocomplete + userOrganizationAutocomplete = new App.UserOrganizationAutocompletion + attribute: + name: 'customer_id' + display: 'Customer' + tag: 'user_autocompletion' + type: 'text' + limit: 200 + null: false + relation: 'User' + autocapitalize: false + disableCreateUser: true + multiple: true + + @$('.userOrganizationAutocompletePlaceholder').replaceWith( userOrganizationAutocomplete.element() ) + # time and timeframe @$('.js-timepicker1, .js-timepicker2').timepicker() diff --git a/app/assets/javascripts/app/lib/app_post/user_organization_autocompletion.coffee b/app/assets/javascripts/app/lib/app_post/user_organization_autocompletion.coffee index 0880cf9ed..d5fa67a58 100644 --- a/app/assets/javascripts/app/lib/app_post/user_organization_autocompletion.coffee +++ b/app/assets/javascripts/app/lib/app_post/user_organization_autocompletion.coffee @@ -4,17 +4,27 @@ class App.UserOrganizationAutocompletion extends App.Controller 'hide.bs.dropdown .js-recipientDropdown': 'hideOrganizationMembers' 'click .js-organization': 'showOrganizationMembers' 'click .js-back': 'hideOrganizationMembers' - 'click .js-user': 'selectUser' + 'click .js-user': 'onUserClick' 'click .js-userNew': 'newUser' - 'focus input': 'open' + 'focus .js-userSelect': 'onFocus' + 'click .js-userSelect': 'stopPropagation' + 'blur .js-userSelect': 'onBlur' + 'click .form-control': 'focusInput' 'click': 'stopPropagation' + 'change .js-userId': 'executeCallback' + 'click .js-remove': 'removeThisToken' elements: '.recipientList': 'recipientList' + '.js-userSelect': 'userSelect' + '.js-userId': 'userId' + '.form-control': 'formControl' constructor: (params) -> super + @lazySearch = _.debounce(@searchUser, 200, true) + @key = Math.floor( Math.random() * 999999 ).toString() if !@attribute.source @@ -23,7 +33,11 @@ class App.UserOrganizationAutocompletion extends App.Controller # set current value if @attribute.value - @setUser(@attribute.value) + if @attribute.multiple and typeof value is 'object' + for value in @attribute.value + @selectUser value, false + else + @selectUser @attribute.value, false element: => @el @@ -32,132 +46,168 @@ class App.UserOrganizationAutocompletion extends App.Controller $(window).off 'click.UserOrganizationAutocompletion' open: => - @clearDelay('close') + # prevent rebinding of keydown event + return if @el.hasClass 'open' + @el.addClass('open') $(window).on 'click.UserOrganizationAutocompletion', @close $(window).on 'keydown.UserOrganizationAutocompletion', @navigateByKeyboard close: => $(window).off 'keydown.UserOrganizationAutocompletion' - execute = => - @el.removeClass('open') - @delay(execute, 50, 'close') + @el.removeClass('open') $(window).off 'click.UserOrganizationAutocompletion' - selectUser: (e) => - userId = $(e.target).parents('.recipientList-entry').data('user-id') - if !userId - userId = $(e.target).data('user-id') - @setUser(userId) + onFocus: => + @formControl.addClass 'focus' + @open() + + focusInput: => + @userSelect.focus() if not @formControl.hasClass 'focus' + + onBlur: => + @formControl.removeClass 'focus' + + onUserClick: (e) => + userId = $(e.currentTarget).data('user-id') + @selectUser(userId) @close() - setUser: (userId) => - @el.find('[name="' + @attribute.name + '"]').val(userId).trigger('change') + selectUser: (userId) => + if @attribute.multiple and @userId.val() + # add userId to end of comma separated list + userId = _.chain( @userId.val().split(',') ).push(userId).join(',').value() + + @userSelect.val('') + @userId.val(userId).trigger('change') executeCallback: => - userId = @el.find('[name="' + @attribute.name + '"]').val() + # with @attribute.multiple this can be several user ids. + # Only work with the last one since its the newest one + userId = @userId.val().split(',').pop() + return if !userId return if !App.User.exists(userId) - user = App.User.find(userId) - name = user.displayName() - if user.email - name += " <#{user.email}>" - @el.find('[name="' + @attribute.name + '_completion"]').val(name).trigger('change') + name = App.User.find(userId).displayName() + + if @attribute.multiple + # create token + @createToken name, userId + else + @userSelect.val(name) if @callback @callback(userId) + createToken: (name, userId) => + @userSelect.before App.view('generic/token')( + name: name + value: userId + ) + + 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 userId from input + index = @$('.token').index(token) + ids = @userId.val().split(',') + ids.splice(index, 1) + @userId.val ids.join(',') + + token.remove() + navigateByKeyboard: (e) => - - # clean input field on ESC - if e.keyCode is 27 - - # if org member selection is shown, go back to member list - if !@recipientList.hasClass('is-shown') - @hideOrganizationMembers() - return - - # empty user selection and close - $(e.target).val('').trigger('change') - - # if tab / close recipientList - if e.keyCode is 9 - @close() - - # ignore arrow keys - if e.keyCode is 37 - return - - if e.keyCode is 39 - return - - # up / select upper item - if e.keyCode is 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 - if e.keyCode is 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 - if e.keyCode is 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) + 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 - userId = recipientListOrganizationMembers.find('li.is-active').data('user-id') - return if !userId - @setUser(userId) - @close() - return - # nav by user list selection - userId = @recipientList.find('li.is-active').data('user-id') - if userId - if userId is 'new' - @newUser() + # empty user selection and close + @userSelect.val('').trigger('change') + # remove last token on backspace + when 8 + if @userSelect.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 - @setUser(userId) - @close() + 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() - organizationId = @recipientList.find('li.is-active').data('organization-id') - return if !organizationId - @showOrganizationMembers(undefined, @recipientList.find('li.is-active')) + # 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 + userId = recipientListOrganizationMembers.find('li.is-active').data('user-id') + return if !userId + @selectUser(userId) + @close() if !@attribute.multiple + return + + # nav by user list selection + userId = @recipientList.find('li.is-active').data('user-id') + if userId + if userId is 'new' + @newUser() + else + @selectUser(userId) + @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) -> @@ -188,33 +238,28 @@ class App.UserOrganizationAutocompletion extends App.Controller if !@attribute.disableCreateUser @recipientList.append(@buildUserNew()) - @el.find('[name="' + @attribute.name + '"]').on( - 'change', - (e) => - @executeCallback() - ) - # start search @searchTerm = '' - @el.find('[name="' + @attribute.name + '_completion"]').on( - 'keyup', - (e) => - term = $(e.target).val().trim() - return if @searchTerm is term - @searchTerm = term - @hideOrganizationMembers() + @userSelect.on 'keyup', @onKeyUp - # hide dropdown - if !term && !@attribute.disableCreateUser - @emptyResultList() - @recipientList.append(@buildUserNew()) + onKeyUp: (e) => + term = $(e.target).val().trim() + return if @searchTerm is term + @searchTerm = term - # show dropdown - if term && ( !@attribute.minLengt || @attribute.minLengt <= term.length ) - execute = => @searchUser(term) - @delay(execute, 400, 'userSearch') - ) + @hideOrganizationMembers() + + # hide dropdown + if !term + @emptyResultList() + + if !@attribute.disableCreateUser + @recipientList.append(@buildUserNew()) + + # show dropdown + if term && ( !@attribute.minLengt || @attribute.minLengt <= term.length ) + @lazySearch(term) searchUser: (term) => @ajax( @@ -249,6 +294,8 @@ class App.UserOrganizationAutocompletion extends App.Controller if !@attribute.disableCreateUser @recipientList.append(@buildUserNew()) + + @recipientList.find('.js-user').first().addClass('is-active') ) emptyResultList: => diff --git a/app/assets/javascripts/app/views/generic/token.jst.eco b/app/assets/javascripts/app/views/generic/token.jst.eco new file mode 100644 index 000000000..d5e6f42ec --- /dev/null +++ b/app/assets/javascripts/app/views/generic/token.jst.eco @@ -0,0 +1,4 @@ +
A date time