@@ -206,7 +206,7 @@ class TokenSelector
206206 true
207207
208208 new_list : ->
209- $list = $ (' <div><ul class="heading"></ul><ul></ul></div>' )
209+ $list = $ (' <div class="list" ><ul class="heading"></ul><ul></ul></div>' )
210210 $list .find (' ul' )
211211 .mousemove (e) =>
212212 return if @ui_locked
@@ -231,11 +231,17 @@ class TokenSelector
231231 @ expand_selection ()
232232 else if @ selection_toggleable () and $ (e .target ).closest (' a.toggle' ).length
233233 @ toggle_selection ()
234- else if not @selection .hasClass (' expanded' )
235- @ toggle_selection (on )
236- @ clear ()
237- @ close ()
234+ else
235+ if @ selection_expanded ()
236+ @ collapse ()
237+ else if @ selection_expandable ()
238+ @ expand_selection ()
239+ else
240+ @ toggle_selection (on )
241+ @ clear ()
242+ @ close ()
238243 @input .focus ()
244+ $list .body = $list .find (' ul' ).last ()
239245 $list
240246
241247 capture_keydown : (e ) ->
@@ -251,12 +257,19 @@ class TokenSelector
251257 @input .remove_last_token ()
252258 return true
253259 when ' Tab' , ' U+0009' , 9
254- @ toggle_selection (on ) if @selection and not @selection .hasClass (' expanded' )
260+ if @selection and (@ selection_toggleable () or not @ selection_expandable ())
261+ @ toggle_selection (on )
255262 @ clear ()
256263 @ close ()
257264 return true if @selection
258265 when ' Enter' , 13
259- if @selection and not @selection .hasClass (' expanded' )
266+ if @ selection_expanded ()
267+ @ collapse ()
268+ return true
269+ else if @ selection_expandable () and not @ selection_toggleable ()
270+ @ expand_selection ()
271+ return true
272+ else if @selection
260273 @ toggle_selection (on )
261274 @ clear ()
262275 @ close ()
@@ -317,7 +330,7 @@ class TokenSelector
317330 @last_applied_query = this_query
318331 @last_search = post_data .search
319332 @ clear_loading ()
320- @ render_list (@query_cache [this_query], options)
333+ @ render_list (@query_cache [this_query], options, post_data )
321334 return
322335
323336 @ set_loading ()
@@ -328,7 +341,7 @@ class TokenSelector
328341 if JSON .stringify (@ prepare_post (options .data ? {})) is this_query # i.e. only if it hasn't subsequently changed (and thus triggered another call)
329342 @last_applied_query = this_query
330343 @last_search = post_data .search
331- @ render_list (data, options) if @menu .is (" :visible" )
344+ @ render_list (data, options, post_data ) if @menu .is (" :visible" )
332345 else
333346 @ui_locked = false
334347 ,
@@ -369,8 +382,8 @@ class TokenSelector
369382 selection_expandable : ->
370383 @selection ? .hasClass (' expandable' ) ? false
371384
372- selection_toggleable : ->
373- @selection ? .hasClass (' toggleable' ) ? false
385+ selection_toggleable : ( $node = @selection ) ->
386+ ( $node ? .hasClass (' toggleable' ) ? false ) and not @ selection_expanded ()
374387
375388 expand_selection : ->
376389 return false unless @ selection_expandable () and not @ selection_expanded ()
@@ -390,19 +403,57 @@ class TokenSelector
390403 @list = $list
391404 @ select $selection
392405 @ui_locked = false
393- # TODO: if any in this list are now selected, we should requery so
394- # as to remove them
395406
396- toggle_selection : (state ) ->
397- return false unless state? or @ selection_toggleable ()
398- id = @selection .data (' id' )
399- state = ! @input . has_token ( value : id ) unless state?
407+ toggle_selection : (state , $node = @selection , toggle_only = false ) ->
408+ return false unless state? or @ selection_toggleable ($node )
409+ id = $node .data (' id' )
410+ state = ! $node . hasClass ( ' on ' ) unless state?
400411 if state
401- @selection .addClass (' on' ) if @ selection_toggleable ()
402- @input .add_token value : id, text : @selection .find (' b' ).text (), no_clear : true , data : @selection .data (' user_data' )
412+ $node .addClass (' on' ) if @ selection_toggleable ($node) and not toggle_only
413+ @input .add_token
414+ value : id
415+ text : $node .data (' text' ) ? $node .text ()
416+ no_clear : true
417+ data : $node .data (' user_data' )
403418 else
404- @selection .removeClass (' on' )
419+ $node .removeClass (' on' ) unless toggle_only
405420 @input .remove_token value : id
421+ @ update_select_all ($node) unless toggle_only
422+
423+ update_select_all : ($node , offset = 0 ) ->
424+ select_all_toggled = $node .data (' user_data' ).select_all
425+ $list = if offset then @stack [@stack .length - offset][1 ] else @list
426+ $select_all = $list .select_all
427+ return unless $select_all
428+ $nodes = $list .body .find (' li.toggleable' ).not ($select_all)
429+ if select_all_toggled
430+ if $select_all .hasClass (' on' )
431+ $nodes .addClass (' on' ).each (i, node) =>
432+ @ toggle_selection off , $ (node), true
433+ else
434+ $nodes .removeClass (' on' ).each (i, node) =>
435+ @ toggle_selection off , $ (node), true
436+ else
437+ $on_nodes = $nodes .filter (' .on' )
438+ if $on_nodes .length < $nodes .length and $select_all .hasClass (' on' )
439+ $select_all .removeClass (' on' )
440+ @ toggle_selection off , $select_all, true
441+ $on_nodes .each (i, node) =>
442+ @ toggle_selection on , $ (node), true
443+ else if $on_nodes .length == $nodes .length and not $select_all .hasClass (' on' )
444+ $select_all .addClass (' on' )
445+ @ toggle_selection on , $select_all, true
446+ $on_nodes .each (i, node) =>
447+ @ toggle_selection off , $ (node), true
448+ if offset < @stack .length
449+ offset++
450+ $parent_node = @stack [@stack .length - offset][0 ]
451+ if @ selection_toggleable ($parent_node)
452+ if $select_all .hasClass (' on' )
453+ $parent_node .addClass (' on' )
454+ else
455+ $parent_node .removeClass (' on' )
456+ @ update_select_all ($parent_node, offset)
406457
407458 select : ($node , preserve_mode = false ) ->
408459 return if $node? [0 ] is @selection ? [0 ]
@@ -459,25 +510,33 @@ class TokenSelector
459510 clear_loading : ->
460511 @list .find (' li' ).first ().loadingImage (' remove' )
461512
462- render_list : (data , options = {}) ->
513+ render_list : (data , options = {}, post_data = {} ) ->
463514 @ open ()
464515
465516 if options .expand
466517 $list = @ new_list ()
467518 else
468519 $list = @list
520+ $list .select_all = null
469521
470522 @selection = null
471523 $uls = $list .find (' ul' )
472524 $uls .html (' ' )
473525 $heading = $uls .first ()
474526 $body = $uls .last ()
475527 if data .length
528+ parent = if @stack .length then @stack [@stack .length - 1 ][0 ] else null
529+ unless data .prepared
530+ @options .preparer ? (post_data, data, parent)
531+ data .prepared = true
532+
476533 for row, i in data
477534 $li = $ (' <li />' ).addClass (' selectable' )
478- @ populate_row ($li, row, {level : @stack .length , first : (i is 0 ), last : (i is data .length - 1 )})
535+ @ populate_row ($li, row, level : @stack .length , first : (i is 0 ), last : (i is data .length - 1 ), parent : parent)
536+ $list .select_all = $li if row .select_all
479537 $li .addClass (' on' ) if $li .hasClass (' toggleable' ) and @input .has_token ($li .data (' id' ))
480538 $body .append ($li)
539+ $list .body .find (' li.toggleable' ).addClass (' on' ) if $list .select_all ? .hasClass ? (' on' ) or @stack .length and @stack [@stack .length - 1 ][0 ].hasClass ? (' on' )
481540 else
482541 $message = $ (' <li class="message first last"></li>' )
483542 $message .text (@options .messages ? .no_results ? ' ' )
@@ -502,13 +561,12 @@ class TokenSelector
502561 @ui_locked = false
503562
504563 prepare_post : (data ) ->
505- post_data = $ .extend (data, {search : @input .val ()})
564+ post_data = $ .extend (data, {search : @input .val ()}, @options . base_data ? {} )
506565 post_data .exclude = @input .base_exclude .concat (if @stack .length then [] else @input .token_values ())
507566 post_data .context = @stack [@stack .length - 1 ][0 ].data (' id' ) if @ list_expanded ()
508567 post_data .per_page ?= @options .limiter ? (level : @stack .length )
509568 post_data
510569
511-
512570# depends on the scrollable ancestor being the first positioned
513571# ancestor. if it's not, it won't work
514572$ .fn .scrollIntoView = (options = {}) ->
@@ -1509,24 +1567,62 @@ I18n.scoped 'conversations', (I18n) ->
15091567 $context_name = if data .context_name then $ (' <span />' , class : ' context_name' ).text (" (#{ context_name} )" ) else ' '
15101568 $b = $ (' <b />' )
15111569 $b .text (data .name )
1512- $span = $ (' <span />' )
1570+ $name = $ (' <span />' , class : ' name' )
1571+ $name .append ($b, $context_name)
1572+ $span = $ (' <span />' , class : ' details' )
15131573 if data .common_courses ?
15141574 $span .text (MessageInbox .context_list (courses : data .common_courses , groups : data .common_groups ))
15151575 else if data .type and data .user_count ?
15161576 $span .text (I18n .t (' people_count' , ' person' , {count : data .user_count }))
1517- $node .append ($b, $context_name, $span)
1577+ else if data .item_count ?
1578+ if data .id .match (/ _groups$ / )
1579+ $span .text (I18n .t (' groups_count' , ' group' , {count : data .item_count }))
1580+ else if data .id .match (/ _sections$ / )
1581+ $span .text (I18n .t (' sections_count' , ' section' , {count : data .item_count }))
1582+ $node .append ($name, $span)
15181583 $node .attr (' title' , data .name )
1584+ text = data .name
1585+ if options .parent
1586+ if data .select_all and data .no_expand # "Select All", e.g. course_123_all -> "Spanish 101: Everyone"
1587+ text = options .parent .data (' text' )
1588+ else if (data .id + ' ' ).match (/ _\d + _/ ) # e.g. course_123_teachers -> "Spanish 101: Teachers"
1589+ text = I18n .beforeLabel (options .parent .data (' text' )) + " " + text
1590+ $node .data (' text' , text)
15191591 $node .data (' id' , data .id )
15201592 $node .data (' user_data' , data)
15211593 $node .addClass (if data .type then data .type else ' user' )
15221594 if options .level > 0
15231595 $node .prepend (' <a class="toggle"><i></i></a>' )
1524- $node .addClass (' toggleable' )
1525- if data .type == ' context'
1596+ $node .addClass (' toggleable' ) unless data . item_count # can't toggle synthetic contexts, e.g. "Student Groups"
1597+ if data .type == ' context' and not data . no_expand
15261598 $node .prepend (' <a class="expand"><i></i></a>' )
15271599 $node .addClass (' expandable' )
15281600 limiter : (options ) ->
15291601 if options .level > 0 then - 1 else 5
1602+ preparer : (post_data , data , parent ) ->
1603+ context = post_data .context
1604+ if not post_data .search and context and data .length > 1
1605+ if context .match (/ ^ (course| section)_\d + $ / )
1606+ # i.e. we are listing synthetic contexts under a course or section
1607+ data .unshift
1608+ id : " #{ context} _all"
1609+ name : I18n .t (' enrollments_everyone' , " Everyone" )
1610+ user_count : parent .data (' user_data' ).user_count
1611+ type : ' context'
1612+ avatar_url : parent .data (' user_data' ).avatar_url
1613+ select_all : true
1614+ else if context .match (/ ^ ((course| section)_\d + _. * | group_\d + )$ / ) and not context .match (/ ^ course_\d + _(groups| sections)$ / )
1615+ # i.e. we are listing all users in a group or synthetic context
1616+ data .unshift
1617+ id : context
1618+ name : I18n .t (' select_all' , " Select All" )
1619+ user_count : parent .data (' user_data' ).user_count
1620+ type : ' context'
1621+ avatar_url : parent .data (' user_data' ).avatar_url
1622+ select_all : true
1623+ no_expand : true # just a magic select-all checkbox, you can't drill into it
1624+ base_data :
1625+ synthetic_contexts : 1
15301626 browser :
15311627 data :
15321628 per_page : - 1
0 commit comments