add multiple option to userOrgSelector

This commit is contained in:
Felix Niklas 2016-04-25 15:14:22 +02:00
parent 39bde93d3f
commit e0324b7e35
7 changed files with 247 additions and 145 deletions

View file

@ -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()

View file

@ -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: =>

View 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>

View file

@ -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>

View file

@ -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,10 +101,16 @@
</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">
<div class="controls">

View file

@ -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

View file

@ -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;
}