diff --git a/lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee b/lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee
index 3e75358d52..91b31cf221 100644
--- a/lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee
+++ b/lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee
@@ -64,7 +64,7 @@ setup_instructor_dashboard = (idash_content) =>
section.addClass CSS_ACTIVE_SECTION
# tracking
- # analytics.pageview "instructor_#{section_name}"
+ analytics.pageview "instructor_section:#{section_name}"
# deep linking
# write to url
diff --git a/lms/static/coffee/src/instructor_dashboard/membership.coffee b/lms/static/coffee/src/instructor_dashboard/membership.coffee
index 48e2531932..733480e268 100644
--- a/lms/static/coffee/src/instructor_dashboard/membership.coffee
+++ b/lms/static/coffee/src/instructor_dashboard/membership.coffee
@@ -7,6 +7,175 @@ plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, argum
std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments
+class MemberListWidget
+ # create a MemberListWidget `$container` is a jquery object to embody.
+ # `params` holds template parameters. `params` should look like the defaults below.
+ constructor: (@$container, params={}) ->
+ params = _.defaults params,
+ title: "Member List"
+ info: """
+ Use this list to manage members.
+ """
+ labels: ["field1", "field2", "field3"]
+ add_placeholder: "Enter name"
+ add_btn_label: "Add Member"
+ add_handler: (input) ->
+
+ template_html = $("#member-list-widget-template").html()
+ @$container.html Mustache.render template_html, params
+
+ # bind info toggle
+ @$('.info-badge').click => @toggle_info()
+
+ # bind add button
+ @$('input[type="button"].add').click =>
+ params.add_handler? @$('.add-field').val()
+
+ show_info: ->
+ @$('.info').show()
+ @$('.member-list').hide()
+
+ show_list: ->
+ @$('.info').hide()
+ @$('.member-list').show()
+
+ toggle_info: ->
+ @$('.info').toggle()
+ @$('.member-list').toggle()
+
+ # clear the input text field
+ clear_input: -> @$('.add-field').val ''
+
+ # clear all table rows
+ clear_rows: -> @$('table tbody').empty()
+
+ # takes a table row as an array items are inserted as text, unless detected
+ # as a jquery objects in which case they are inserted directly. if an
+ # element is a jquery object
+ add_row: (row_array) ->
+ $tbody = @$('table tbody')
+ $tr = $ '
'
+ for item in row_array
+ $td = $ ''
+ if item instanceof jQuery
+ $td.append item
+ else
+ $td.text item
+ $tr.append $td
+ $tbody.append $tr
+
+ # local selector
+ $: (selector) ->
+ if @debug?
+ s = @$container.find selector
+ if s?.length != 1
+ console.warn "local selector '#{selector}' found (#{s.length}) results"
+ s
+ else
+ @$container.find selector
+
+
+class AuthListWidget extends MemberListWidget
+ constructor: ($container, @rolename, @$error_section) ->
+ super $container,
+ title: $container.data 'display-name'
+ info: $container.data 'info-text'
+ labels: ["username", "email", "revoke access"]
+ add_placeholder: "Enter email"
+ add_btn_label: $container.data 'add-button-label'
+ add_handler: (input) => @add_handler input
+
+ @debug = true
+ @list_endpoint = $container.data 'list-endpoint'
+ @modify_endpoint = $container.data 'modify-endpoint'
+ unless @rolename?
+ throw "AuthListWidget missing @rolename"
+
+ @reload_list()
+
+ # action to do when is reintroduced into user's view
+ re_view: ->
+ @clear_errors()
+ @clear_input()
+ @reload_list()
+ @$('.info').hide()
+ @$('.member-list').show()
+
+ # handle clicks on the add button
+ add_handler: (input) ->
+ if input? and input isnt ''
+ @modify_member_access input, 'allow', (error) =>
+ # abort on error
+ return @show_errors error unless error is null
+ @clear_errors()
+ @clear_input()
+ @reload_list()
+ else
+ @show_errors "Enter an email."
+
+ # reload the list of members
+ reload_list: ->
+ # @clear_rows()
+ # @show_info()
+ @get_member_list (error, member_list) =>
+ # abort on error
+ return @show_errors error unless error is null
+
+ # only show the list of there are members
+ @clear_rows()
+ @show_info()
+ # @show_info()
+
+ # use _.each instead of 'for' so that member
+ # is bound in the button callback.
+ _.each member_list, (member) =>
+ # if there are members, show the list
+
+ # create revoke button and insert it into the row
+ $revoke_btn = $ '',
+ class: 'revoke'
+ click: =>
+ @modify_member_access member.email, 'revoke', (error) =>
+ # abort on error
+ return @show_errors error unless error is null
+ @clear_errors()
+ @reload_list()
+ @add_row [member.username, member.email, $revoke_btn]
+ # make sure the list is shown because there are members.
+ @show_list()
+
+ # clear error display
+ clear_errors: -> @$error_section?.text ''
+
+ # set error display
+ show_errors: (msg) -> @$error_section?.text msg
+
+ # send ajax request to list members
+ # `cb` is called with cb(error, member_list)
+ get_member_list: (cb) ->
+ $.ajax
+ dataType: 'json'
+ url: @list_endpoint
+ data: rolename: @rolename
+ success: (data) => cb? null, data[@rolename]
+ error: std_ajax_err => cb? "Error fetching list for role '#{@rolename}'"
+
+ # send ajax request to modify access
+ # (add or remove them from the list)
+ # `action` can be 'allow' or 'revoke'
+ # `cb` is called with cb(error, data)
+ modify_member_access: (email, action, cb) ->
+ $.ajax
+ dataType: 'json'
+ url: @modify_endpoint
+ data:
+ email: email
+ rolename: @rolename
+ action: action
+ success: (data) => cb? null, data
+ error: std_ajax_err => cb? "Error changing user's permissions."
+
+
# Wrapper for the batch enrollment subsection.
# This object handles buttons, success and failure reporting,
# and server communication.
@@ -277,13 +446,15 @@ class Membership
plantTimeout 0, => new BatchEnrollment @$section.find '.batch-enrollment'
# gather elements
- @$list_selector = @$section.find('select#member-lists-selector')
+ @$list_selector = @$section.find 'select#member-lists-selector'
+ @$auth_list_containers = @$section.find '.auth-list-container'
+ @$auth_list_errors = @$section.find '.member-lists-management .request-response-error'
# initialize & store AuthList subsections
# one for each .auth-list-container in the section.
- @auth_lists = _.map (@$section.find '.auth-list-container'), (auth_list_container) ->
+ @auth_lists = _.map (@$auth_list_containers), (auth_list_container) =>
rolename = $(auth_list_container).data 'rolename'
- new AuthList $(auth_list_container), rolename
+ new AuthListWidget $(auth_list_container), rolename, @$auth_list_errors
# populate selector
@$list_selector.empty()
@@ -299,17 +470,14 @@ class Membership
for auth_list in @auth_lists
auth_list.$container.removeClass 'active'
auth_list = $opt.data('auth_list')
- auth_list.refresh()
auth_list.$container.addClass 'active'
+ auth_list.re_view()
# one-time first selection of top list.
@$list_selector.change()
-
# handler for when the section title is clicked.
onClickTitle: ->
- for auth_list in @auth_lists
- auth_list.refresh()
# export for use
diff --git a/lms/static/images/info-icon-dark.png b/lms/static/images/info-icon-dark.png
new file mode 100644
index 0000000000..4f22a5dac9
Binary files /dev/null and b/lms/static/images/info-icon-dark.png differ
diff --git a/lms/static/sass/course/instructor/_instructor_2.scss b/lms/static/sass/course/instructor/_instructor_2.scss
index 3540ad8e21..58ac22c7b9 100644
--- a/lms/static/sass/course/instructor/_instructor_2.scss
+++ b/lms/static/sass/course/instructor/_instructor_2.scss
@@ -18,92 +18,91 @@
right: 15px;
font-size: 11pt;
}
+}
- section.instructor-dashboard-content-2 {
- @extend .content;
- // position: relative;
- padding: 40px;
- width: 100%;
+section.instructor-dashboard-content-2 {
+ @extend .content;
+ // position: relative;
+ padding: 40px;
+ width: 100%;
- // .has-event-handler-for-click {
- // border: 1px solid blue;
- // }
+ // .has-event-handler-for-click {
+ // border: 1px solid blue;
+ // }
- .request-response-error {
- margin-top: 1em;
- margin-bottom: 1em;
- color: $error-red;
+ .request-response-error {
+ margin-top: 1em;
+ margin-bottom: 1em;
+ color: $error-red;
+ }
+
+ .slickgrid {
+ margin-left: 1px;
+ color:#333333;
+ font-size:11px;
+ font-family: verdana,arial,sans-serif;
+
+ .slick-header-column {
+ // height: 100%
}
- .slickgrid {
- margin-left: 1px;
- color:#333333;
- font-size:11px;
- font-family: verdana,arial,sans-serif;
-
- .slick-header-column {
- // height: 100%
- }
-
- .slick-cell {
- border: 1px dotted silver;
- border-collapse: collapse;
- white-space: normal;
- word-wrap: break-word;
- }
- }
-
- h1 {
- @extend .top-header;
- padding-bottom: 0;
- border-bottom: 0;
- }
-
- input[type="button"] {
- @include idashbutton(#eee);
-
- &.molly-guard {
- // @include idashbutton($danger-red);
- // @include idashbutton($black);
- // border: 2px solid $danger-red;
- }
- }
-
- .instructor_dash_glob_info {
- position: absolute;
- top: 46px;
- right: 50px;
- text-align: right;
- }
-
- .instructor-nav {
- padding-bottom: 1em;
-
- border-bottom: 1px solid #C8C8C8;
- a {
- margin-right: 1.2em;
- }
-
- .active-section {
- color: #551A8B;
- }
- }
-
- section.idash-section {
- display: none;
- // background-color: #0f0;
-
- &.active-section {
- display: block;
- // background-color: #ff0;
- }
-
- .basic-data {
- padding: 6px;
- }
+ .slick-cell {
+ border: 1px dotted silver;
+ border-collapse: collapse;
+ white-space: normal;
+ word-wrap: break-word;
+ }
+ }
+
+ h1 {
+ @extend .top-header;
+ padding-bottom: 0;
+ border-bottom: 0;
+ }
+
+ input[type="button"] {
+ @include idashbutton(#eee);
+
+ &.molly-guard {
+ // @include idashbutton($danger-red);
+ // @include idashbutton($black);
+ // border: 2px solid $danger-red;
+ }
+ }
+
+ .instructor_dash_glob_info {
+ position: absolute;
+ top: 46px;
+ right: 50px;
+ text-align: right;
+ }
+
+ .instructor-nav {
+ padding-bottom: 1em;
+
+ border-bottom: 1px solid #C8C8C8;
+ a {
+ margin-right: 1.2em;
+ }
+
+ .active-section {
+ color: #551A8B;
+ }
+ }
+
+ section.idash-section {
+ display: none;
+ // background-color: #0f0;
+
+ &.active-section {
+ display: block;
+ // background-color: #ff0;
+ }
+
+ .basic-data {
+ padding: 6px;
}
}
- // @extend .table-wrapper;
}
@@ -250,14 +249,20 @@
display: block;
}
- .auth-list-table {
- .slickgrid {
- height: 250px;
- }
- }
+ .revoke {
+ width: 10px;
+ height: 10px;
+ background: url('../images/moderator-delete-icon.png') left center no-repeat;
+ opacity: 0.7;
+ &:hover { opacity: 0.8; }
+ &:active { opacity: 0.9; }
- .auth-list-add {
- margin-top: 0.5em;
+ // @include idashbutton($danger-red);
+ // line-height: 0.6em;
+ // margin-top: 5px;
+ // padding: 6px 9px;
+ // font-size: 9pt;
+ // border-radius: 10px;
}
}
}
@@ -329,3 +334,115 @@
}
}
}
+
+
+.member-list-widget {
+ $width: 20 * $baseline;
+ $height: 25 * $baseline;
+ $header-height: 3 * $baseline;
+ $bottom-bar-height: 3 * $baseline;
+ $content-height: $height - $header-height - $bottom-bar-height;
+ $border-radius: 3px;
+
+ width: $width;
+ height: $height;
+
+ .header {
+ @include box-sizing(border-box);
+ @include border-top-radius($border-radius);
+
+ position: relative;
+ padding: $baseline;
+ width: $width;
+ height: $header-height;
+
+ background-color: #efefef;
+ border: 1px solid $light-gray;
+ }
+
+ .title {
+ font-size: 16pt;
+ }
+
+ .label {
+ color: $lighter-base-font-color;
+ font-size: $body-font-size * 4/5;
+ }
+
+ .info-badge {
+ // float: right;
+ position: absolute;
+ top: $baseline / 2;
+ right: $baseline / 2;
+ width: 17px;
+ height: 17px;
+ background: url('../images/info-icon-dark.png') left center no-repeat;
+ opacity: 0.35;
+ &:hover { opacity: 0.45; }
+ &:active { opacity: 0.5; }
+ }
+
+ .info {
+ display: none;
+
+ @include box-sizing(border-box);
+ max-height: $content-height;
+ padding: $baseline;
+
+ border: 1px solid $light-gray;
+ border-top: none;
+ color: $lighter-base-font-color;
+ line-height: 1.3em;
+ }
+
+ .member-list {
+ @include box-sizing(border-box);
+ overflow: auto;
+ padding-top: 0;
+ width: $width;
+ max-height: $content-height;
+
+ table {
+ width: 100%;
+ }
+
+ tr {
+ border-bottom: 1px solid $light-gray;
+ }
+
+ td {
+ table-layout: fixed;
+ vertical-align: middle;
+ word-wrap: break-word;
+ padding-left: 15px;
+ border-left: 1px solid $light-gray;
+ border-right: 1px solid $light-gray;
+
+ font-size: 3/4 *$body-font-size;
+ }
+ }
+
+ .bottom-bar {
+ @include box-sizing(border-box);
+ @include border-bottom-radius($border-radius);
+
+ position: relative;
+ width: $width;
+ height: $bottom-bar-height;
+ padding: $baseline / 2;
+
+ // border-top: none;
+ margin-top: -1px;
+ border: 1px solid $light-gray;
+ background-color: #efefef;
+ box-shadow: inset #bbb 0px 1px 1px 0px;
+ }
+
+ // .add-field
+
+ input[type="button"].add {
+ @include idashbutton($blue);
+ position: absolute;
+ right: $baseline;
+ }
+}
diff --git a/lms/templates/courseware/instructor_dashboard_2/instructor_dashboard_2.html b/lms/templates/courseware/instructor_dashboard_2/instructor_dashboard_2.html
index 116f81b0e5..e670a30b3e 100644
--- a/lms/templates/courseware/instructor_dashboard_2/instructor_dashboard_2.html
+++ b/lms/templates/courseware/instructor_dashboard_2/instructor_dashboard_2.html
@@ -5,6 +5,7 @@
<%block name="headextra">
<%static:css group='course'/>
+
diff --git a/lms/templates/courseware/instructor_dashboard_2/membership.html b/lms/templates/courseware/instructor_dashboard_2/membership.html
index d50e71f9fb..c226f74215 100644
--- a/lms/templates/courseware/instructor_dashboard_2/membership.html
+++ b/lms/templates/courseware/instructor_dashboard_2/membership.html
@@ -1,5 +1,31 @@
<%page args="section_data"/>
+
+
Batch Enrollment
Enter student emails separated by new lines or commas.
@@ -26,66 +52,75 @@
+
+
%if section_data['access']['instructor']:
-
+
%if section_data['access']['instructor']:
-
+
%endif
-
+
%endif
%if section_data['access']['instructor']:
-
+
%endif
%if section_data['access']['instructor'] or section_data['access']['forum_admin']:
-
+
-
+
%endif
|