Skip to content

Commit

Permalink
multiple contexts per appointment group
Browse files Browse the repository at this point in the history
closes #7561

Test plan:
  - create some appointment groups:
    - make one with multiple courses
    - make one with a course, and then add more courses after saving
    - make one restricted to some (not all) sections in a course [1]
    - make one that has students sign up in groups [1]
  - make sure that only students that match the above criteria are able
    to see/reserve those appointment groups

  [1] you can only choose the group-signup option or restrict
      appointment groups to certain sections on creation (these options
      won't be availabe when editing later)

Change-Id: I1cff5fb4ed233882c2061f8fd8a7ba6f2d4959b0
Reviewed-on: https://gerrit.instructure.com/9407
Tested-by: Jenkins <[email protected]>
Reviewed-by: Jon Jensen <[email protected]>
  • Loading branch information
cmatheson committed Apr 30, 2012
1 parent 81593a3 commit a2a6579
Show file tree
Hide file tree
Showing 71 changed files with 879 additions and 308 deletions.
162 changes: 162 additions & 0 deletions app/coffeescripts/calendar/ContextSelector.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
define [
'jquery'
'underscore'
'jst/calendar/contextSelector'
'jst/calendar/contextSelectorItem'
'compiled/fn/preventDefault'
], ($, _, contextSelectorTemplate, contextSelectorItemTemplate, preventDefault) ->

class ContextSelectorItem
constructor: (@context) ->
@state = 'off'
@locked = false
@sectionsLocked = false

pubsubHelper = (fn) =>
(sender) =>
return if sender is this
fn.apply(this)

$.subscribe '/contextSelector/disable', pubsubHelper(@disable)
$.subscribe '/contextSelector/enable', pubsubHelper(@enable)
$.subscribe '/contextSelector/uncheck', pubsubHelper(=> @setState('off'))

$.subscribe '/contextSelector/lockSections', @lockSections

render: ($list) ->
@$listItem = $(contextSelectorItemTemplate(@context))
@$listItem.appendTo($list)
@$sectionsList = @$listItem.find('.ag_sections')

@$listItem.find('.ag_sections_toggle').click preventDefault @toggleSections
@$contentCheckbox = @$listItem.find('[name="context_codes[]"]')
@$contentCheckbox.change preventDefault @change
@$sectionCheckboxes = @$listItem.find('[name="sections[]"]')
@$sectionCheckboxes.change @sectionChange

toggleSections: (jsEvent) =>
$(jsEvent.target).toggleClass('ag-sections-expanded')
@$sectionsList.toggleClass('hidden')

change: =>
newState = switch @state
when 'off' then 'on'
when 'on' then 'off'
when 'partial' then 'on'
@setState(newState)

setState: (state) =>
return if @locked

@state = state
switch @state
when 'on', 'off'
checked = @state == 'on'
@$contentCheckbox.prop('checked', checked)
@$contentCheckbox.prop('indeterminate', false)
@$sectionCheckboxes.prop('checked', checked)
$.publish("/contextSelector/enable", [this])
when 'partial'
@$contentCheckbox.prop('checked', true)
@$contentCheckbox.prop('indeterminate', true)
$.publish('/contextSelector/disable', [this])
$.publish('/contextSelector/uncheck', [this])

$.publish('/contextSelector/changed')

sectionChange: =>
switch @$sectionCheckboxes.filter(':checked').length
when 0
@setState('off')
when @$sectionCheckboxes.length
@setState('on')
else
@setState('partial')

disable: ->
@$contentCheckbox.prop('disabled', true)
@disableSections()

disableSections: ->
@$sectionCheckboxes.prop('disabled', true)

enable: ->
unless @locked
@$contentCheckbox.prop('disabled', false)
@enableSections()

enableSections: ->
unless @lockedSections
@$sectionCheckboxes.prop('disabled', false)

lock: ->
@locked = true
@disable()
$.publish('/contextSelector/lockSections')

lockSections: =>
@lockedSections = true
@disableSections()

isChecked: -> @state != 'off'

sections: ->
checked = @$sectionCheckboxes.filter(':checked')
if checked.length == @$sectionCheckboxes.length
[]
else
_.map(checked, (cb) -> cb.value)

class ContextSelector
constructor: (selector, @apptGroup, @contexts, contextsChangedCB, closeCB) ->
@$menu = $(selector).html contextSelectorTemplate()
$contextsList = @$menu.find('.ag-contexts')

$.subscribe('/contextSelector/changed', => contextsChangedCB @selectedContexts(), @selectedSections())

@contextSelectorItems = {}
for c in @contexts
item = new ContextSelectorItem(c)
item.render($contextsList)
@contextSelectorItems[item.context.asset_string] = item

if @apptGroup.sub_context_codes.length > 0
# if you choose sub_contexts when creating an appointment
# group, the appointment group is locked down
# TODO: be smarter about this
for subContextCode in @apptGroup.sub_context_codes
$("[value='#{subContextCode}']").prop('checked', true)
for c, item of @contextSelectorItems
item.sectionChange()
item.lock()
else
for contextCode in @apptGroup.context_codes
@contextSelectorItems[contextCode].setState('on')
@contextSelectorItems[contextCode].lock()

$('.ag_contexts_done').click preventDefault closeCB

contextsChangedCB(@selectedContexts(), @selectedSections())

selectedContexts: ->
contexts = _.chain(@contextSelectorItems)
.values()
.filter( (c) -> c.state != 'off')
.map( (c) -> c.context.asset_string)
.value()

numPartials = _.filter(contexts, (c) -> c.state == 'partial')
if numPartials > 1 or numPartials == 1 and contexts.length > 1
throw "invalid state"

contexts

selectedSections: ->
sections = _.chain(@contextSelectorItems)
.values()
.map( (c) -> c.sections())
.reject((ss) -> ss.length == 0)
.value()

throw "invalid state" if sections.length > 1
sections[0]
122 changes: 75 additions & 47 deletions app/coffeescripts/calendar/EditAppointmentGroupDetails.coffee
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
define [
'jquery'
'underscore'
'i18n!EditAppointmentGroupDetails'
'compiled/calendar/TimeBlockList'
'jst/calendar/editAppointmentGroup'
'jst/calendar/genericSelect'
'jst/calendar/sectionCheckboxes'
'compiled/calendar/ContextSelector'
'compiled/fn/preventDefault'
'jquery.ajaxJSON'
'jquery.disableWhileLoading'
'jquery.instructure_forms'
], ($, I18n, TimeBlockList, editAppointmentGroupTemplate, genericSelectTemplate, sectionCheckboxesTemplate) ->
], ($, _, I18n, TimeBlockList, editAppointmentGroupTemplate, genericSelectTemplate, sectionCheckboxesTemplate, ContextSelector, preventDefault) ->

class EditAppointmentGroupDetails
constructor: (selector, @apptGroup, @contextChangeCB, @closeCB) ->
constructor: (selector, @apptGroup, @contexts, @closeCB) ->
@currentContextInfo = null

$(selector).html editAppointmentGroupTemplate({
title: @apptGroup.title
contexts: @apptGroup.contexts
contexts: @contexts
appointment_group: @apptGroup
})

@contextsHash = {}
@contextsHash[c.asset_string] = c for c in @contexts

@form = $(selector).find("form")

@form.find("select.context_id").change(@contextChange).change()
@contextSelector = new ContextSelector('.ag-menu-container', @apptGroup, @contexts, @contextsChanged, @toggleContextsMenu)

if @apptGroup.id
@form.attr('action', @apptGroup.url)
Expand All @@ -29,16 +37,17 @@ define [
@form.find(".context_id").val(@apptGroup.context_code).attr('disabled', true)
@form.find("select.context_id").change()

@form.find(".group_category").attr('disabled', true)
@form.find('input[name="section_ids[]"]').attr('disabled', true)
@form.find(".group-signup-checkbox").attr('disabled', true)
@disableGroups()
if @apptGroup.participant_type == 'Group'
@form.find(".group-signup-checkbox").prop('checked', true)
@form.find(".group_category").val(@apptGroup.sub_context_codes[0])
else
@form.find(".group-signup-checkbox").prop('checked', false)
else
@form.attr('action', @currentContextInfo.create_appointment_group_url)
# FIXME: put this url in ENV json or something
@form.attr('action', '/api/v1/appointment_groups')

@form.find('.ag_contexts_selector').click preventDefault @toggleContextsMenu

timeBlocks = ([appt.start, appt.end, true] for appt in @apptGroup.appointmentEvents || [] )
@timeBlockList = new TimeBlockList(@form.find(".time-block-list-body"), @form.find(".splitter"), timeBlocks)
Expand All @@ -54,7 +63,6 @@ define [
checked = !!jsEvent.target.checked
@form.find('.per_appointment_groups_label').toggle(checked)
@form.find('.per_appointment_users_label').toggle(!checked)
@form.find(".section-signup").toggle(!checked)
@form.find(".group-signup").toggle(checked)
@form.find(".group-signup-checkbox").change()

Expand Down Expand Up @@ -86,12 +94,6 @@ define [
if @apptGroup.workflow_state == 'active'
@form.find("#appointment-blocks-active-button").attr('disabled', true).prop('checked', true)

contextInfoForCode: (code) ->
for context in @apptGroup.contexts
if context.asset_string == code
return context
return null

saveWithoutPublishingClick: (jsEvent) =>
jsEvent.preventDefault()
@save(false)
Expand Down Expand Up @@ -136,13 +138,23 @@ define [

params['appointment_group[participant_visibility]'] = if data.participant_visibility == '1' then 'protected' else 'private'

if create
params['appointment_group[context_code]'] = data.context_code
# get the context/section info from @contextSelector instead
delete data['context_codes[]']
delete data['sections[]']

contextCodes = @contextSelector.selectedContexts()
if contextCodes.length == 0
$('.ag_contexts_selector').errorBox(I18n.t 'context_required', 'You need to select a calendar')
return
else
params['appointment_group[context_codes]'] = contextCodes

if create
if data.use_group_signup == '1' && data.group_category_id
params['appointment_group[sub_context_codes]'] = [data.group_category_id]
else if data['section_ids[]']?.length > 0
params['appointment_group[sub_context_codes]'] = data['section_ids[]']
else
sections = @contextSelector.selectedSections()
params['appointment_group[sub_context_codes]'] = sections if sections

# TODO: Provide UI for specifying this
params['appointment_group[min_appointments_per_participant]'] = 1
Expand All @@ -155,31 +167,47 @@ define [
deferred = $.ajaxJSON @form.attr('action'), method, params, onSuccess, onError
@form.disableWhileLoading(deferred)

contextChange: (jsEvent) =>
context = $(jsEvent.target).val()
@currentContextInfo = @contextInfoForCode(context)
@apptGroup.contextInfo = @currentContextInfo
if @currentContextInfo == null then return

# Update the sections and groups lists in the scheduler
if @currentContextInfo.course_sections
for courseSection in @currentContextInfo.course_sections
courseSection.selected = courseSection.asset_string in this.apptGroup.sub_context_codes
sectionsInfo = { sections: @currentContextInfo.course_sections }
@form.find('.section_select').html(sectionCheckboxesTemplate(sectionsInfo))

if !@currentContextInfo.group_categories || @currentContextInfo.group_categories.length == 0
@form.find(".group-signup-checkbox").attr('disabled', true).prop('checked', false).change()
else if @currentContextInfo.group_categories
@form.find(".group-signup-checkbox").attr('disabled', false)
groupsInfo =
cssClass: 'group_category'
name: 'group_category_id'
collection: @currentContextInfo.group_categories
@form.find(".group_select").html(genericSelectTemplate(groupsInfo))

@contextChangeCB(context)

# Update the edit and more options links with the new context, if this is a new group
if !@apptGroup.id
@form.attr('action', @currentContextInfo.create_appointment_group_url)
contextsChanged: (contextCodes, sectionCodes) =>
# dropdown text
if sectionCodes
sectionCode = sectionCodes[0]
section = _.chain(@contexts)
.pluck('course_sections')
.flatten()
.find((s) -> s.asset_string == sectionCode)
.value()
text = section.name
if sectionCodes.length > 1
text += I18n.t('and_n_sectionCodes', ' and %{n} others', n: sectionCodes.length - 1)
@form.find('.ag_contexts_selector').text(text)
else if contextCodes.length > 0
contextCode = contextCodes[0]
text = @contextsHash[contextCode].name
if contextCodes.length > 1
text += I18n.t('and_n_contexts', ' and %{n} others', n: contextCodes.length - 1)
@form.find('.ag_contexts_selector').text(text)
else
@form.find('.ag_contexts_selector').text(I18n.t 'select_calendars', 'Select Calendars')

# group selector
context = @contextsHash[contextCodes[0]]
if contextCodes.length == 1 and not sectionCodes and context.group_categories?.length > 0
@enableGroups(context)
else
@disableGroups()

disableGroups: ->
@form.find(".group-signup-checkbox").attr('disabled', true)
@form.find("group-signup").hide()

enableGroups: (contextInfo) ->
@form.find(".group-signup-checkbox").attr('disabled', false)
groupsInfo =
cssClass: 'group_category'
name: 'group_category_id'
collection: contextInfo.group_categories
@form.find(".group_select").html(genericSelectTemplate(groupsInfo))
@form.find("group-signup").show()

toggleContextsMenu: (jsEvent) =>
$('.ag_contexts_menu').toggleClass('hidden')
10 changes: 5 additions & 5 deletions app/coffeescripts/calendar/EditAppointmentGroupDialog.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ define [
width: 'auto'
resizable: false
title: I18n.t('titles.edit_appointment_group', "Edit Appointment Group")
# this is dumb, but it prevents the columns from wrapping when
# the context selector drop down gets too long
dialog.dialog('widget').find('#edit_event').css('overflow', 'visible')

class EditAppointmentGroupDialog
constructor: (@apptGroup, @parentCloseCB) ->
constructor: (@apptGroup, @contexts, @parentCloseCB) ->
@currentContextInfo = null

contextChange: (newContext) =>
# TODO: update the color?

closeCB: (saved) =>
dialog.dialog('close')
@parentCloseCB(saved)

show: =>
@appointmentGroupsForm = new EditAppointmentGroupDetails(dialog.find(".wrapper"), @apptGroup, @contextChange, @closeCB)
@appointmentGroupsForm = new EditAppointmentGroupDetails(dialog.find(".wrapper"), @apptGroup, @contexts, @closeCB)

buttons = if @apptGroup.workflow_state == 'active'
[
Expand Down
Loading

0 comments on commit a2a6579

Please sign in to comment.