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() ) @$('.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 # time and timeframe
@$('.js-timepicker1, .js-timepicker2').timepicker() @$('.js-timepicker1, .js-timepicker2').timepicker()

View file

@ -4,17 +4,27 @@ class App.UserOrganizationAutocompletion extends App.Controller
'hide.bs.dropdown .js-recipientDropdown': 'hideOrganizationMembers' 'hide.bs.dropdown .js-recipientDropdown': 'hideOrganizationMembers'
'click .js-organization': 'showOrganizationMembers' 'click .js-organization': 'showOrganizationMembers'
'click .js-back': 'hideOrganizationMembers' 'click .js-back': 'hideOrganizationMembers'
'click .js-user': 'selectUser' 'click .js-user': 'onUserClick'
'click .js-userNew': 'newUser' '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' 'click': 'stopPropagation'
'change .js-userId': 'executeCallback'
'click .js-remove': 'removeThisToken'
elements: elements:
'.recipientList': 'recipientList' '.recipientList': 'recipientList'
'.js-userSelect': 'userSelect'
'.js-userId': 'userId'
'.form-control': 'formControl'
constructor: (params) -> constructor: (params) ->
super super
@lazySearch = _.debounce(@searchUser, 200, true)
@key = Math.floor( Math.random() * 999999 ).toString() @key = Math.floor( Math.random() * 999999 ).toString()
if !@attribute.source if !@attribute.source
@ -23,7 +33,11 @@ class App.UserOrganizationAutocompletion extends App.Controller
# set current value # set current value
if @attribute.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: => element: =>
@el @el
@ -32,132 +46,168 @@ class App.UserOrganizationAutocompletion extends App.Controller
$(window).off 'click.UserOrganizationAutocompletion' $(window).off 'click.UserOrganizationAutocompletion'
open: => open: =>
@clearDelay('close') # prevent rebinding of keydown event
return if @el.hasClass 'open'
@el.addClass('open') @el.addClass('open')
$(window).on 'click.UserOrganizationAutocompletion', @close $(window).on 'click.UserOrganizationAutocompletion', @close
$(window).on 'keydown.UserOrganizationAutocompletion', @navigateByKeyboard $(window).on 'keydown.UserOrganizationAutocompletion', @navigateByKeyboard
close: => close: =>
$(window).off 'keydown.UserOrganizationAutocompletion' $(window).off 'keydown.UserOrganizationAutocompletion'
execute = => @el.removeClass('open')
@el.removeClass('open')
@delay(execute, 50, 'close')
$(window).off 'click.UserOrganizationAutocompletion' $(window).off 'click.UserOrganizationAutocompletion'
selectUser: (e) => onFocus: =>
userId = $(e.target).parents('.recipientList-entry').data('user-id') @formControl.addClass 'focus'
if !userId @open()
userId = $(e.target).data('user-id')
@setUser(userId) focusInput: =>
@userSelect.focus() if not @formControl.hasClass 'focus'
onBlur: =>
@formControl.removeClass 'focus'
onUserClick: (e) =>
userId = $(e.currentTarget).data('user-id')
@selectUser(userId)
@close() @close()
setUser: (userId) => selectUser: (userId) =>
@el.find('[name="' + @attribute.name + '"]').val(userId).trigger('change') 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: => 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 !userId
return if !App.User.exists(userId) return if !App.User.exists(userId)
user = App.User.find(userId) name = App.User.find(userId).displayName()
name = user.displayName()
if user.email if @attribute.multiple
name += " <#{user.email}>" # create token
@el.find('[name="' + @attribute.name + '_completion"]').val(name).trigger('change') @createToken name, userId
else
@userSelect.val(name)
if @callback if @callback
@callback(userId) @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) => navigateByKeyboard: (e) =>
switch e.keyCode
# clean input field on ESC # clean input on esc
if e.keyCode is 27 when 27
# if org member selection is shown, go back to member list
# if org member selection is shown, go back to member list if !@recipientList.hasClass('is-shown')
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)
@hideOrganizationMembers() @hideOrganizationMembers()
return return
userId = recipientListOrganizationMembers.find('li.is-active').data('user-id')
return if !userId
@setUser(userId)
@close()
return
# nav by user list selection # empty user selection and close
userId = @recipientList.find('li.is-active').data('user-id') @userSelect.val('').trigger('change')
if userId # remove last token on backspace
if userId is 'new' when 8
@newUser() 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 else
@setUser(userId) if recipientListOrgMemeber.not('.hide').find('li.is-active').prev().length isnt 0
@close() recipientListOrgMemeber.not('.hide').find('li.is-active').removeClass('is-active').prev().addClass('is-active')
return 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') # nav by org member selection
return if !organizationId if !@recipientList.hasClass('is-shown')
@showOrganizationMembers(undefined, @recipientList.find('li.is-active')) 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) -> buildOrganizationItem: (organization) ->
@ -188,33 +238,28 @@ class App.UserOrganizationAutocompletion extends App.Controller
if !@attribute.disableCreateUser if !@attribute.disableCreateUser
@recipientList.append(@buildUserNew()) @recipientList.append(@buildUserNew())
@el.find('[name="' + @attribute.name + '"]').on(
'change',
(e) =>
@executeCallback()
)
# start search # start search
@searchTerm = '' @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 onKeyUp: (e) =>
if !term && !@attribute.disableCreateUser term = $(e.target).val().trim()
@emptyResultList() return if @searchTerm is term
@recipientList.append(@buildUserNew()) @searchTerm = term
# show dropdown @hideOrganizationMembers()
if term && ( !@attribute.minLengt || @attribute.minLengt <= term.length )
execute = => @searchUser(term) # hide dropdown
@delay(execute, 400, 'userSearch') if !term
) @emptyResultList()
if !@attribute.disableCreateUser
@recipientList.append(@buildUserNew())
# show dropdown
if term && ( !@attribute.minLengt || @attribute.minLengt <= term.length )
@lazySearch(term)
searchUser: (term) => searchUser: (term) =>
@ajax( @ajax(
@ -249,6 +294,8 @@ class App.UserOrganizationAutocompletion extends App.Controller
if !@attribute.disableCreateUser if !@attribute.disableCreateUser
@recipientList.append(@buildUserNew()) @recipientList.append(@buildUserNew())
@recipientList.find('.js-user').first().addClass('is-active')
) )
emptyResultList: => 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"> <div class="tokenfield form-control u-positionOrigin">
<input type="hidden" name="<%- @attribute.name %>" value="<%= @attribute.value %>"> <input class="js-userId" type="hidden" name="<%- @attribute.name %>" tabindex="-1">
<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"> <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') %> <%- @Icon('arrow-down', 'dropdown-arrow') %>
</div> </div>

View file

@ -34,9 +34,11 @@
<p>A date time</p> <p>A date time</p>
<div class="datetime form-group"> <div class="datetime form-group">
<div class="controls controls--bundle"> <div class="controls controls--bundle">
<input type="text" value="<%- @Tdate('2015-10-28') %>" class="form-control js-datepicker4"> <div class="u-positionOrigin horizontal">
<div class="controls-label">at</div> <input type="text" value="<%- @Tdate('2015-10-28') %>" class="form-control js-datepicker4">
<input type="text" value="08:00" class="form-control time js-timepicker4"> <div class="controls-label">at</div>
<input type="text" value="08:00" class="form-control time js-timepicker4">
</div>
</div> </div>
</div> </div>
@ -99,9 +101,15 @@
</div> </div>
<div class="select form-group"> <div class="select form-group">
<label for="b">Users (searchable ajax)</label> <label for="b">User (searchable ajax)</label>
<div class="searchableAjaxSelectPlaceholder"></div> <div class="searchableAjaxSelectPlaceholder"></div>
</div> </div>
<h2>User</h2>
<div class="select form-group">
<label for="b">User Select</label>
<div class="userOrganizationAutocompletePlaceholder"></div>
</div>
<h2>Checkbox</h2> <h2>Checkbox</h2>
<div class="checkbox form-group"> <div class="checkbox form-group">

View file

@ -6,7 +6,6 @@
*= require ./bootstrap.css *= require ./bootstrap.css
*= require ./cropper.css *= require ./cropper.css
*= require ./fineuploader.css *= require ./fineuploader.css
*= require ./bootstrap-tokenfield.css
*= require ./font.css *= require ./font.css
*= require ./svg-dimensions.css *= require ./svg-dimensions.css
*= require ./highlighter-github.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, .form-control + .icon-arrow-down,
.dropdown-arrow { .dropdown-arrow {
position: absolute; position: absolute;
@ -5382,54 +5389,74 @@ footer {
fill: white; fill: white;
} }
.tokenfield .token { .token {
padding: 0 0 0 10px; padding: 0 0 0 10px;
margin: 0 5px 7px 0; margin: 0 5px 6px 0;
height: 25px; height: 26px;
line-height: 27px;
color: white; color: white;
border-radius: 3px;
background: hsl(198,19%,72%); background: hsl(198,19%,72%);
border: none; border: none;
float: none; float: none;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
cursor: default;
} }
/*
selector needs to be stronger than .token-input
in order to override input[type=text]
*/
.tokenfield .token-input { .tokenfield .token-input {
vertical-align: top; vertical-align: top;
padding: 0 10px 7px 0; padding: 0 10px 7px 5px;
margin: 0; margin: 0;
min-width: 60px;
height: 32px; height: 32px;
display: inline-block; display: inline-block;
border: none;
box-shadow: none;
outline: none;
flex: 1;
&:focus {
box-shadow: none;
}
} }
.tokenfield .token ~ .token-input { .tokenfield .token ~ .token-input {
padding: 0 5px 7px 0; padding: 0 5px 7px 0;
} }
.tokenfield .token .token-label { .token-label {
padding: 0; padding: 0;
} }
.tokenfield.form-control { .tokenfield.form-control {
padding: 7px 7px 0; padding: 7px 7px 0;
height: auto;
display: flex;
flex-wrap: wrap;
} }
.tokenfield .token .close { .token .close,
.token-close {
margin: 0; margin: 0;
padding: 0 9px 0 5px; padding: 0 9px 0 12px;
font-family: inherit; font-family: inherit;
font-weight: 300; font-weight: 100;
font-size: 30px; font-size: 28px;
line-height: 1; line-height: 1;
color: white; color: white;
text-shadow: none; text-shadow: none;
opacity: .3; opacity: .3;
outline: none; outline: none;
height: auto; height: auto;
cursor: pointer;
} }
.tokenfield .token .close:hover { .token .close:hover,
.token-close:hover {
opacity: .5; opacity: .5;
} }
@ -5862,7 +5889,8 @@ footer {
opacity: 1; opacity: 1;
} }
.recipientList-entry:hover .recipientList-icon { .recipientList-entry:hover .recipientList-icon,
.recipientList-entry.is-active .recipientList-icon {
opacity: 1; opacity: 1;
} }