add multiple option to userOrgSelector
This commit is contained in:
parent
39bde93d3f
commit
e0324b7e35
7 changed files with 247 additions and 145 deletions
|
@ -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()
|
||||
|
||||
|
|
|
@ -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: =>
|
||||
|
|
4
app/assets/javascripts/app/views/generic/token.jst.eco
Normal file
4
app/assets/javascripts/app/views/generic/token.jst.eco
Normal file
|
@ -0,0 +1,4 @@
|
|||
<div class="token" data-value="<%= @value %>">
|
||||
<span class="token-label"><%= @name %></span>
|
||||
<span class="token-close js-remove">×</span>
|
||||
</div>
|
|
@ -1,6 +1,6 @@
|
|||
<div class="u-positionOrigin">
|
||||
<input type="hidden" name="<%- @attribute.name %>" value="<%= @attribute.value %>">
|
||||
<input name="<%- @attribute.name %>_completion" class="user-select form-control" autocapitalize="off" placeholder="<%- @attribute.placeholder %>" autocomplete="off" role="textbox" aria-autocomplete="list" aria-haspopup="true">
|
||||
<div class="tokenfield form-control u-positionOrigin">
|
||||
<input class="js-userId" type="hidden" name="<%- @attribute.name %>" tabindex="-1">
|
||||
<input name="<%- @attribute.name %>_completion" class="user-select token-input js-userSelect" autocapitalize="off" placeholder="<%- @attribute.placeholder %>" autocomplete="off" role="textbox" aria-autocomplete="list" aria-haspopup="true">
|
||||
<%- @Icon('arrow-down', 'dropdown-arrow') %>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -34,9 +34,11 @@
|
|||
<p>A date time</p>
|
||||
<div class="datetime form-group">
|
||||
<div class="controls controls--bundle">
|
||||
<input type="text" value="<%- @Tdate('2015-10-28') %>" class="form-control js-datepicker4">
|
||||
<div class="controls-label">at</div>
|
||||
<input type="text" value="08:00" class="form-control time js-timepicker4">
|
||||
<div class="u-positionOrigin horizontal">
|
||||
<input type="text" value="<%- @Tdate('2015-10-28') %>" class="form-control js-datepicker4">
|
||||
<div class="controls-label">at</div>
|
||||
<input type="text" value="08:00" class="form-control time js-timepicker4">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -99,9 +101,15 @@
|
|||
</div>
|
||||
|
||||
<div class="select form-group">
|
||||
<label for="b">Users (searchable ajax)</label>
|
||||
<label for="b">User (searchable ajax)</label>
|
||||
<div class="searchableAjaxSelectPlaceholder"></div>
|
||||
</div>
|
||||
|
||||
<h2>User</h2>
|
||||
<div class="select form-group">
|
||||
<label for="b">User Select</label>
|
||||
<div class="userOrganizationAutocompletePlaceholder"></div>
|
||||
</div>
|
||||
|
||||
<h2>Checkbox</h2>
|
||||
<div class="checkbox form-group">
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*= require ./bootstrap.css
|
||||
*= require ./cropper.css
|
||||
*= require ./fineuploader.css
|
||||
*= require ./bootstrap-tokenfield.css
|
||||
*= require ./font.css
|
||||
*= require ./svg-dimensions.css
|
||||
*= require ./highlighter-github.css
|
||||
|
|
|
@ -1639,6 +1639,13 @@ select.form-control:not([multiple]) {
|
|||
}
|
||||
}
|
||||
|
||||
.select.form-group,
|
||||
.user_autocompletion.form-group {
|
||||
.form-control {
|
||||
padding-right: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-control + .icon-arrow-down,
|
||||
.dropdown-arrow {
|
||||
position: absolute;
|
||||
|
@ -5382,54 +5389,74 @@ footer {
|
|||
fill: white;
|
||||
}
|
||||
|
||||
.tokenfield .token {
|
||||
.token {
|
||||
padding: 0 0 0 10px;
|
||||
margin: 0 5px 7px 0;
|
||||
height: 25px;
|
||||
line-height: 27px;
|
||||
margin: 0 5px 6px 0;
|
||||
height: 26px;
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
background: hsl(198,19%,72%);
|
||||
border: none;
|
||||
float: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*
|
||||
selector needs to be stronger than .token-input
|
||||
in order to override input[type=text]
|
||||
*/
|
||||
.tokenfield .token-input {
|
||||
vertical-align: top;
|
||||
padding: 0 10px 7px 0;
|
||||
padding: 0 10px 7px 5px;
|
||||
margin: 0;
|
||||
min-width: 60px;
|
||||
height: 32px;
|
||||
display: inline-block;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
flex: 1;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tokenfield .token ~ .token-input {
|
||||
padding: 0 5px 7px 0;
|
||||
}
|
||||
|
||||
.tokenfield .token .token-label {
|
||||
.token-label {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tokenfield.form-control {
|
||||
padding: 7px 7px 0;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tokenfield .token .close {
|
||||
.token .close,
|
||||
.token-close {
|
||||
margin: 0;
|
||||
padding: 0 9px 0 5px;
|
||||
padding: 0 9px 0 12px;
|
||||
font-family: inherit;
|
||||
font-weight: 300;
|
||||
font-size: 30px;
|
||||
font-weight: 100;
|
||||
font-size: 28px;
|
||||
line-height: 1;
|
||||
color: white;
|
||||
text-shadow: none;
|
||||
opacity: .3;
|
||||
outline: none;
|
||||
height: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tokenfield .token .close:hover {
|
||||
.token .close:hover,
|
||||
.token-close:hover {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
|
@ -5862,7 +5889,8 @@ footer {
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
.recipientList-entry:hover .recipientList-icon {
|
||||
.recipientList-entry:hover .recipientList-icon,
|
||||
.recipientList-entry.is-active .recipientList-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue