Commit bb7eed58 by Carlos de la Guardia Committed by cewing

MIT: CCX. Refactore using backbone.js

parent 96ca1da0
...@@ -317,6 +317,16 @@ def get_poc_schedule(course, poc): ...@@ -317,6 +317,16 @@ def get_poc_schedule(course, poc):
@ensure_csrf_cookie @ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True) @cache_control(no_cache=True, no_store=True, must_revalidate=True)
@coach_dashboard @coach_dashboard
def poc_schedule(request, course):
poc = get_poc_for_coach(course, request.user)
schedule = get_poc_schedule(course, poc)
json_schedule = json.dumps(schedule, indent=4)
return HttpResponse(json_schedule, mimetype='application/json')
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@coach_dashboard
def poc_invite(request, course): def poc_invite(request, course):
""" """
Invite users to new poc Invite users to new poc
......
...@@ -1203,6 +1203,8 @@ reverify_js = [ ...@@ -1203,6 +1203,8 @@ reverify_js = [
'js/verify_student/incourse_reverify.js', 'js/verify_student/incourse_reverify.js',
] ]
pocs_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/pocs/**/*.js'))
PIPELINE_CSS = { PIPELINE_CSS = {
'style-vendor': { 'style-vendor': {
'source_filenames': [ 'source_filenames': [
...@@ -1396,6 +1398,10 @@ PIPELINE_JS = { ...@@ -1396,6 +1398,10 @@ PIPELINE_JS = {
'reverify': { 'reverify': {
'source_filenames': reverify_js, 'source_filenames': reverify_js,
'output_filename': 'js/reverify.js' 'output_filename': 'js/reverify.js'
},
'pocs': {
'source_filenames': pocs_js,
'output_filename': 'js/pocs.js'
} }
} }
......
var edx = edx || {};
(function($, _, Backbone, gettext) {
'use strict';
edx.pocs = edx.pocs || {};
edx.pocs.schedule = edx.pocs.schedule || {};
var syncErrorMessage = gettext("The data could not be saved.");
var self;
edx.pocs.schedule.reloadPage = function() {
location.reload();
};
edx.pocs.schedule.UnitModel = Backbone.Model.extend({
defaults: {
location: '',
display_name: '',
start: null,
due: null,
category: '',
hidden: false,
children: []
},
});
edx.pocs.schedule.Schedule = Backbone.Collection.extend({
model: edx.pocs.schedule.UnitModel,
url: 'poc_schedule'
});
edx.pocs.schedule.ScheduleView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'render');
this.schedule_collection = new edx.pocs.schedule.Schedule();
this.schedule = {};
this.schedule_collection.bind('reset', this.render);
this.schedule_collection.fetch({reset: true});
this.chapter_select = $('form#add-unit select[name="chapter"]'),
this.sequential_select = $('form#add-unit select[name="sequential"]'),
this.vertical_select = $('form#add-unit select[name="vertical"]');
this.dirty = false;
self = this;
$('#add-all').on('click', function(event) {
event.preventDefault();
this.schedule_apply(self.schedule, show);
self.dirty = true;
self.render();
});
},
render: function() {
this.schedule = this.schedule_collection.toJSON();
this.hidden = this.pruned(this.schedule, function(node) {
return node.hidden || node.category !== 'vertical'});
this.showing = this.pruned(this.schedule, function(node) {
return !node.hidden});
this.$el.html(schedule_template({chapters: this.showing}));
$('table.poc-schedule .sequential,.vertical').hide();
$('table.poc-schedule .toggle-collapse').on('click', this.toggle_collapse);
//
// Hidden hover fields for empty date fields
$('table.poc-schedule .date a').each(function() {
if (! $(this).text()) {
$(this).text('Set date').addClass('empty');
}
});
// Handle date edit clicks
$('table.poc-schedule .date a').attr('href', '#enter-date-modal')
.leanModal({closeButton: '.close-modal'});
$('table.poc-schedule .due-date a').on('click', this.enterNewDate('due'));
$('table.poc-schedule .start-date a').on('click', this.enterNewDate('start'));
// Click handler for remove all
$('table.poc-schedule a#remove-all').on('click', function(event) {
event.preventDefault();
this.schedule_apply(self.schedule, hide);
self.dirty = true;
self.render();
});
// Show or hide form
if (this.hidden.length) {
// Populate chapters select, depopulate others
this.chapter_select.html('')
.append('<option value="none">'+gettext("Select a chapter")+'...</option>')
.append(this.schedule_options(this.hidden));
this.sequential_select.html('').prop('disabled', true);
this.vertical_select.html('').prop('disabled', true);
$('form#add-unit').show();
$('#all-units-added').hide();
$('#add-unit-button').prop('disabled', true);
}
else {
$('form#add-unit').hide();
$('#all-units-added').show();
}
// Add unit handlers
this.chapter_select.on('change', function(event) {
var chapter_location = self.chapter_select.val();
self.vertical_select.html('').prop('disabled', true);
if (chapter_location !== 'none') {
var chapter = self.find_unit(self.hidden, chapter_location);
self.sequential_select.html('')
.append('<option value="all">'+gettext("All subsections")+'</option>')
.append(self.schedule_options(chapter.children));
self.sequential_select.prop('disabled', false);
$('#add-unit-button').prop('disabled', false);
self.set_datetime('start', chapter.start);
self.set_datetime('due', chapter.due);
}
else {
self.sequential_select.html('').prop('disabled', true);
}
});
this.sequential_select.on('change', function(event) {
var sequential_location = self.sequential_select.val();
if (sequential_location !== 'all') {
var chapter = self.chapter_select.val();
sequential = self.find_unit(self.hidden, chapter, sequential_location);
self.vertical_select.html('')
.append('<option value="all">'+gettext("All units")+'</option>')
.append(schedule_options(sequential.children));
self.vertical_select.prop('disabled', false);
self.set_datetime('start', sequential.start);
self.set_datetime('due', sequential.due);
}
else {
self.vertical_select.html('').prop('disabled', true);
}
});
this.vertical_select.on('change', function(event) {
var vertical_location = self.vertical_select.val();
if (vertical_location !== 'all') {
var chapter = chapter_select.val(),
sequential = self.sequential_select.val();
vertical = self.find_unit(
self.hidden, chapter, sequential, vertical_location);
self.set_datetime('start', vertical.start);
self.set_datetime('due', vertical.due);
}
});
// Add unit handler
$('#add-unit-button').on('click', function(event) {
event.preventDefault();
var chapter = self.chapter_select.val(),
sequential = self.sequential_select.val(),
vertical = self.vertical_select.val(),
units = self.find_lineage(self.schedule,
chapter,
sequential == 'all' ? null : sequential,
vertical == 'all' ? null: vertical),
start = self.get_datetime('start'),
due = self.get_datetime('due');
units.map(show);
unit = units[units.length - 1]
self.schedule_apply([unit], show);
if (start) unit.start = start;
if (due) unit.due = due;
self.dirty = true;
self.render();
});
// Remove unit handler
$('table.poc-schedule a.remove-unit').on('click', function(event) {
var row = $(this).closest('tr'),
path = row.data('location').split(' '),
unit = self.find_unit(self.schedule, path[0], path[1], path[2]);
self.schedule_apply([unit], self.hide);
self.dirty = true;
self.render();
});
// Show or hide save button
if (this.dirty) $('#dirty-schedule').show()
else $('#dirty-schedule').hide();
// Handle save button
$('#dirty-schedule #save-changes').on('click', function(event) {
event.preventDefault();
self.save();
});
$('#ajax-error').hide();
return this;
},
save: function() {
var button = $('#dirty-schedule #save-changes');
button.prop('disabled', true).text(gettext("Saving")+'...');
$.ajax({
url: save_url,
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(this.schedule),
success: function(data, textStatus, jqXHR) {
self.schedule = data.schedule;
self.dirty = false;
self.render();
button.prop('disabled', false).text(gettext("Save changes"));
// Update textarea with grading policy JSON, since grading policy
// may have changed.
$('#grading-policy').text(data.grading_policy);
},
error: function(jqXHR, textStatus, error) {
console.log(jqXHR.responseText);
$('#ajax-error').show();
$('#dirty-schedule').hide();
$('form#add-unit select,input,button').prop('disabled', true);
}
});
},
hide: function(unit) {
unit.hidden = true;
},
show: function(unit) {
unit.hidden = false;
},
get_datetime: function(which) {
var date = $('form#add-unit input[name=' + which + '_date]').val();
var time = $('form#add-unit input[name=' + which + '_time]').val();
if (date && time)
return date + ' ' + time;
return null;
},
set_datetime: function(which, value) {
var parts = value ? value.split(' ') : ['', ''],
date = parts[0],
time = parts[1];
$('form#add-unit input[name=' + which + '_date]').val(date);
$('form#add-unit input[name=' + which + '_time]').val(time);
},
schedule_options: function(nodes) {
return nodes.map(function(node) {
return $('<option>')
.attr('value', node.location)
.text(node.display_name)[0];
});
},
schedule_apply: function(nodes, f) {
nodes.map(function(node) {
f(node);
if (node.children !== undefined) self.schedule_apply(node.children, f);
});
},
pruned: function(tree, filter) {
return tree.filter(filter)
.map(function(node) {
var copy = {};
$.extend(copy, node);
if (node.children) copy.children = self.pruned(node.children, filter);
return copy;
})
.filter(function(node) {
return node.children === undefined || node.children.length;
});
},
toggle_collapse: function(event) {
event.preventDefault();
var row = $(this).closest('tr');
var children = self.get_children(row);
if (row.is('.expanded')) {
$(this).removeClass('icon-caret-down').addClass('icon-caret-right');
row.removeClass('expanded').addClass('collapsed');
children.hide();
}
else {
$(this).removeClass('icon-caret-right').addClass('icon-caret-down');
row.removeClass('collapsed').addClass('expanded');
children.filter('.collapsed').each(function() {
children = children.not(self.get_children(this));
});
children.show();
}
},
enterNewDate: function(what) {
return function(event) {
var row = $(this).closest('tr');
var modal = $('#enter-date-modal')
.data('what', what)
.data('location', row.data('location'));
modal.find('h2').text(
what == 'due' ? gettext("Enter Due Date") :
gettext("Enter Start Date"));
modal.find('label').text(row.find('td:first').text());
var path = row.data('location').split(' '),
unit = self.find_unit(self.schedule, path[0], path[1], path[2]),
parts = unit[what] ? unit[what].split(' ') : ['', ''],
date = parts[0],
time = parts[1];
modal.find('input[name=date]').val(date);
modal.find('input[name=time]').val(time);
modal.find('form').off('submit').on('submit', function(event) {
event.preventDefault();
var date = $(this).find('input[name=date]').val(),
time = $(this).find('input[name=time]').val();
var valid_date = new Date(date);
if (isNaN(valid_date.valueOf())) {
alert('Please enter a valid date');
return;
}
var valid_time = /^\d{1,2}:\d{2}?$/;
if (!time.match(valid_time)) {
alert('Please enter a valid time');
return;
}
unit[what] = date + ' ' + time;
modal.find('.close-modal').click();
self.dirty = true;
self.render();
});
}
},
find_unit: function(tree, chapter, sequential, vertical) {
var units = self.find_lineage(tree, chapter, sequential, vertical);
return units[units.length -1];
},
find_lineage: function(tree, chapter, sequential, vertical) {
function find_in(seq, location) {
for (var i = 0; i < seq.length; i++)
if (seq[i].location === location)
return seq[i];
}
var units = [],
unit = find_in(tree, chapter);
units[units.length] = unit;
if (sequential) {
units[units.length] = unit = find_in(unit.children, sequential);
if (vertical)
units[units.length] = unit = find_in(unit.children, vertical);
}
return units;
},
get_children: function(row) {
var depth = $(row).data('depth');
return $(row).nextUntil(
$(row).siblings().filter(function() {
return $(this).data('depth') <= depth;
})
);
}
});
edx.pocs.schedule.XScheduleView = Backbone.View.extend({
events: {
'submit': 'submit',
'change': 'change'
},
initialize: function() {
_.bindAll(this, 'render', 'change', 'submit', 'invalidProfile', 'invalidPreference', 'error', 'sync', 'clearStatus');
this.scheduleModel = new edx.pocs.schedule.ProfileModel();
this.scheduleModel.on('invalid', this.invalidProfile);
this.scheduleModel.on('error', this.error);
this.scheduleModel.on('sync', this.sync);
this.preferencesModel = new edx.pocs.schedule.PreferencesModel();
this.preferencesModel.on('invalid', this.invalidPreference);
this.preferencesModel.on('error', this.error);
this.preferencesModel.on('sync', this.sync);
},
render: function() {
this.$el.html(_.template($('#schedule-tpl').html()));
this.$nameField = $('#schedule-name', this.$el);
this.$nameStatus = $('#schedule-name-status', this.$el);
this.$languageChoices = $('#preference-language', this.$el);
this.$languageStatus = $('#preference-language-status', this.$el);
this.$submitStatus = $('#submit-status', this.$el);
var self = this;
$.getJSON('preferences/languages')
.done(function(json) {
/** Asynchronously populate the language choices. */
self.$languageChoices.html(_.template($('#languages-tpl').html(), {languageInfo: json}));
})
.fail(function() {
self.$languageStatus
.addClass('language-list-error')
.text(gettext("We couldn't populate the list of language choices."));
});
return this;
},
change: function() {
this.scheduleModel.set({
fullName: this.$nameField.val()
});
this.preferencesModel.set({
language: this.$languageChoices.val()
});
},
submit: function(event) {
event.preventDefault();
this.clearStatus();
this.scheduleModel.save();
this.preferencesModel.save();
},
invalidProfile: function(model) {
var errors = model.validationError;
if (errors.hasOwnProperty('fullName')) {
this.$nameStatus
.addClass('validation-error')
.text(errors.fullName);
}
},
invalidPreference: function(model) {
var errors = model.validationError;
if (errors.hasOwnProperty('language')) {
this.$languageStatus
.addClass('validation-error')
.text(errors.language);
}
},
error: function(error) {
this.$submitStatus
.addClass('error')
.text(error);
},
sync: function() {
this.$submitStatus
.addClass('success')
.text(gettext("Saved"));
},
clearStatus: function() {
this.$nameStatus
.removeClass('validation-error')
.text("");
this.$languageStatus
.removeClass('validation-error')
.text("");
this.$submitStatus
.removeClass('error')
.text("");
}
});
})(jQuery, _, Backbone, gettext);
...@@ -14,16 +14,6 @@ ...@@ -14,16 +14,6 @@
<%static:css group='style-course'/> <%static:css group='style-course'/>
</%block> </%block>
<%block name="jsextra">
<script src="${static.url('js/vendor/timepicker/jquery.timepicker.js')}"></script>
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
<style>
.ui-timepicker-list { z-index: 100000; }
.ui-datepicker { z-index: 100000 !important; }
input.date, input.time { width: auto !important; display: inline !important; }
</style>
</%block>
<%include file="/courseware/course_navigation.html" args="active_page='poc_coach'" /> <%include file="/courseware/course_navigation.html" args="active_page='poc_coach'" />
<section class="container"> <section class="container">
...@@ -148,5 +138,4 @@ ...@@ -148,5 +138,4 @@
$(setup_tabs); $(setup_tabs);
$(setup_management_form) $(setup_management_form)
</script> </script>
<%! from django.utils.translation import ugettext as _ %> <%! from django.utils.translation import ugettext as _ %>
<script id="poc-schedule-template" type="text/x-handlerbars-template"> <%namespace name='static' file='/static_content.html'/>
<table class="poc-schedule">
<thead> <%block name="jsextra">
<tr> <script>
<th>${_('Unit')}</th> var save_url = '${save_url}';
<th>${_('Start Date')}</th> var schedule = ${schedule};
<th>${_('Due Date')}</th> </script>
<th><a href="#" id="remove-all"> <script src="${static.url('js/vendor/backbone-min.js')}"></script>
<i class="icon-remove-sign icon"></i> ${_('remove all')} <script src="${static.url('js/vendor/timepicker/jquery.timepicker.js')}"></script>
</a></th> <link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
</tr> <style>
</thead> .ui-timepicker-list { z-index: 100000; }
<tbody> .ui-datepicker { z-index: 100000 !important; }
{{#each chapters}} input.date, input.time { width: auto !important; display: inline !important; }
<tr class="chapter collapsed" data-location="{{location}}" data-depth="1"> </style>
<td class="unit"> <%static:js group='pocs'/>
<a href="#"><i class="icon-caret-right icon toggle-collapse"></i></a> </%block>
{{display_name}}
</td> %for template_name in ["schedule"]:
<td class="date start-date"><a>{{start}}</a></td> <script type="text/template" id="poc-${template_name}-template">
<td class="date due-date"><a>{{due}}</a></td> <%static:include path="pocs/${template_name}.underscore" />
<td><a href="#" class="remove-unit"> </script>
<i class="icon-remove-sign icon"></i> ${_('remove')} %endfor
</a></td>
</tr>
{{#each children}}
<tr class="sequential collapsed" data-depth="2"
data-location="{{../location}} {{location}}">
<td class="unit">
<a href="#"><i class="icon-caret-right icon toggle-collapse"></i></a>
{{display_name}}
</td>
<td class="date start-date"><a>{{start}}</a></td>
<td class="date due-date"><a>{{due}}</a></td>
<td><a href="#" class="remove-unit">
<i class="icon-remove-sign icon"></i> ${_('remove')}
</a></td>
</tr>
{{#each children}}
<tr class="vertical" data-dapth="3"
data-location="{{../../location}} {{../location}} {{location}}">
<td class="unit">&nbsp;{{display_name}}</td>
<td class="date start-date"><a>{{start}}</a></td>
<td class="date due-date"><a>{{due}}</a></td>
<td><a href="#" class="remove-unit">
<i class="icon-remove-sign icon"></i> ${_('remove')}
</a></td>
{{/each}}
{{/each}}
{{/each}}
</tbody>
</table>
</script>
<div class="poc-schedule-container"> <div class="poc-schedule-container">
<div id="poc-schedule"></div> <div id="poc-schedule"></div>
<div id="new-poc-schedule"></div>
</div> </div>
<section id="enter-date-modal" class="modal" aria-hidden="true"> <section id="enter-date-modal" class="modal" aria-hidden="true">
...@@ -70,7 +41,7 @@ ...@@ -70,7 +41,7 @@
<h2></h2> <h2></h2>
</header> </header>
<form role="form"> <form role="form">
<div class="field" id="datepair"> <div class="field datepair">
<label></label> <label></label>
<input placeholder="Date" class="date" type="text" name="date"/ size="11"> <input placeholder="Date" class="date" type="text" name="date"/ size="11">
<input placeholder="Time" class="time" type="text" name="time"/ size="6"> <input placeholder="Time" class="time" type="text" name="time"/ size="6">
...@@ -101,26 +72,26 @@ ...@@ -101,26 +72,26 @@
<h2>${_('Schedule a Unit')}</h2> <h2>${_('Schedule a Unit')}</h2>
<form role="form" id="add-unit" name="add-unit" class="poc-form"> <form role="form" id="add-unit" name="add-unit" class="poc-form">
<div class="field"> <div class="field">
<b>${_('Chapter')}</b><br/> <b>${_('Section')}</b><br/>
<select name="chapter"></select> <select name="chapter"></select>
</div> </div>
<div class="field"> <div class="field">
<b>${_('Sequential')}</b><br/> <b>${_('Subsection')}</b><br/>
<select name="sequential"></select> <select name="sequential"></select>
</div> </div>
<div class="field"> <div class="field">
<b>${_('Vertical')}</b><br/> <b>${_('Unit')}</b><br/>
<select name="vertical"></select> <select name="vertical"></select>
</div> </div>
<div class="field"> <div class="field datepair">
<b>${_('Start Date')}</b><br/> <b>${_('Start Date')}</b><br/>
<input type="date" name="start_date"/> <input placeholder="Date" type="date" class="date" name="start_date"/>
<input type="time" name="start_time"/> <input placeholder="time" type="time" class="time" name="start_time"/>
</div> </div>
<div class="field"> <div class="field datepair">
<b>${_('Due Date')}</b> ${_('(Optional)')}<br/> <b>${_('Due Date')}</b> ${_('(Optional)')}<br/>
<input type="date" name="due_date"/> <input placeholder="Date" type="date" class="date" name="due_date"/>
<input type="time" name="due_time"/> <input placeholder="time" type="time" class="time" name="due_time"/>
</div> </div>
<div class="field"> <div class="field">
<br/> <br/>
...@@ -138,390 +109,20 @@ ...@@ -138,390 +109,20 @@
</div> </div>
<script> <script>
$('#datepair .time').timepicker({ $(function() {
schedule_template = _.template($('#poc-schedule-template').html());
var view = new edx.pocs.schedule.ScheduleView({
el: $('#new-poc-schedule')
});
view.render();
//poc_schedule.render();
$('.datepair .time').timepicker({
'showDuration': true, 'showDuration': true,
'timeFormat': 'G:i' 'timeFormat': 'G:i'
}); });
$('#datepair .date').datepicker({ $('.datepair .date').datepicker({
'dateFormat': 'yy-mm-dd', 'dateFormat': 'yy-mm-dd',
'autoclose': true 'autoclose': true
});
var poc_schedule = (function () {
var save_url = '${save_url}';
var schedule = ${schedule};
var template = Handlebars.compile($('#poc-schedule-template').html());
var chapter_select = $('form#add-unit select[name="chapter"]'),
sequential_select = $('form#add-unit select[name="sequential"]'),
vertical_select = $('form#add-unit select[name="vertical"]');
var self = {
schedule: schedule,
dirty: false
};
function hide(unit) {
unit.hidden = true;
}
function show(unit) {
unit.hidden = false;
}
function get_datetime(which) {
var date = $('form#add-unit input[name=' + which + '_date]').val();
var time = $('form#add-unit input[name=' + which + '_time]').val();
if (date && time)
return date + ' ' + time;
return null;
}
function set_datetime(which, value) {
var parts = value ? value.split(' ') : ['', ''],
date = parts[0],
time = parts[1];
$('form#add-unit input[name=' + which + '_date]').val(date);
$('form#add-unit input[name=' + which + '_time]').val(time);
}
/**
* Render the course tree view.
*/
self.render = function() {
this.hidden = pruned(this.schedule, function(node) {
return node.hidden || node.category !== 'vertical'});
this.showing = pruned(this.schedule, function(node) {
return !node.hidden});
// Render template
$('#poc-schedule').html(template({chapters: this.showing}));
// Start collapsed
$('table.poc-schedule .sequential,.vertical').hide();
// Click handlers for collapsible tree
$('table.poc-schedule .toggle-collapse').on('click', toggle_collapse);
// Hidden hover fields for empty date fields
$('table.poc-schedule .date a').each(function() {
if (! $(this).text()) {
$(this).text('Set date').addClass('empty');
}
});
// Handle date edit clicks
$('table.poc-schedule .date a').attr('href', '#enter-date-modal')
.leanModal({closeButton: '.close-modal'});
$('table.poc-schedule .due-date a').on('click', enterNewDate('due'));
$('table.poc-schedule .start-date a').on('click', enterNewDate('start'));
// Click handler for remove all
$('table.poc-schedule a#remove-all').on('click', function(event) {
event.preventDefault();
schedule_apply(self.schedule, hide);
self.dirty = true;
self.render();
});
// Show or hide form
if (this.hidden.length) {
// Populate chapters select, depopulate others
chapter_select.html('')
.append('<option value="none">${_("Select a chapter")}...</option>')
.append(schedule_options(this.hidden));
sequential_select.html('').prop('disabled', true);
vertical_select.html('').prop('disabled', true);
$('form#add-unit').show();
$('#all-units-added').hide();
$('#add-unit-button').prop('disabled', true);
}
else {
$('form#add-unit').hide();
$('#all-units-added').show();
}
// Add unit handlers
chapter_select.on('change', function(event) {
var chapter_location = chapter_select.val();
vertical_select.html('').prop('disabled', true);
if (chapter_location !== 'none') {
chapter = find_unit(self.hidden, chapter_location);
sequential_select.html('')
.append('<option value="all">${_("All sequentials")}</option>')
.append(schedule_options(chapter.children));
sequential_select.prop('disabled', false);
$('#add-unit-button').prop('disabled', false);
set_datetime('start', chapter.start);
set_datetime('due', chapter.due);
}
else {
sequential_select.html('').prop('disabled', true);
}
});
sequential_select.on('change', function(event) {
var sequential_location = sequential_select.val();
if (sequential_location !== 'all') {
var chapter = chapter_select.val();
sequential = find_unit(self.hidden, chapter, sequential_location);
vertical_select.html('')
.append('<option value="all">${_("All verticals")}</option>')
.append(schedule_options(sequential.children));
vertical_select.prop('disabled', false);
set_datetime('start', sequential.start);
set_datetime('due', sequential.due);
}
else {
vertical_select.html('').prop('disabled', true);
}
});
vertical_select.on('change', function(event) {
var vertical_location = vertical_select.val();
if (vertical_location !== 'all') {
var chapter = chapter_select.val(),
sequential = sequential_select.val();
vertical = find_unit(
self.hidden, chapter, sequential, vertical_location);
set_datetime('start', vertical.start);
set_datetime('due', vertical.due);
}
});
// Add unit handler
$('#add-unit-button').on('click', function(event) {
event.preventDefault();
var chapter = chapter_select.val(),
sequential = sequential_select.val(),
vertical = vertical_select.val(),
units = find_lineage(self.schedule,
chapter,
sequential == 'all' ? null : sequential,
vertical == 'all' ? null: vertical),
start = get_datetime('start'),
due = get_datetime('due');
units.map(show);
unit = units[units.length - 1]
schedule_apply([unit], show);
if (start) unit.start = start;
if (due) unit.due = due;
self.dirty = true;
self.render();
}); });
});
// Remove unit handler
$('table.poc-schedule a.remove-unit').on('click', function(event) {
var row = $(this).closest('tr'),
path = row.data('location').split(' '),
unit = find_unit(self.schedule, path[0], path[1], path[2]);
schedule_apply([unit], hide);
self.dirty = true;
self.render();
});
// Show or hide save button
if (this.dirty) $('#dirty-schedule').show()
else $('#dirty-schedule').hide();
// Handle save button
$('#dirty-schedule #save-changes').on('click', function(event) {
event.preventDefault();
self.save();
});
$('#ajax-error').hide();
}
/**
* Handle date entry.
*/
function enterNewDate(what) {
return function(event) {
var row = $(this).closest('tr');
var modal = $('#enter-date-modal')
.data('what', what)
.data('location', row.data('location'));
modal.find('h2').text(
what == 'due' ? '${_("Enter Due Date")}' :
'${_("Enter Start Date")}');
modal.find('label').text(row.find('td:first').text());
var path = row.data('location').split(' '),
unit = find_unit(self.schedule, path[0], path[1], path[2]),
parts = unit[what] ? unit[what].split(' ') : ['', ''],
date = parts[0],
time = parts[1];
modal.find('input[name=date]').val(date);
modal.find('input[name=time]').val(time);
modal.find('form').off('submit').on('submit', function(event) {
event.preventDefault();
var date = $(this).find('input[name=date]').val(),
time = $(this).find('input[name=time]').val();
var valid_date = new Date(date);
if (isNaN(valid_date.valueOf())) {
alert('Please enter a valid date');
return;
}
var valid_time = /^\d{1,2}:\d{2}?$/;
if (!time.match(valid_time)) {
alert('Please enter a valid time');
return;
}
unit[what] = date + ' ' + time;
modal.find('.close-modal').click();
self.dirty = true;
self.render();
});
}
}
/**
* Returns a sequence of <option> DOM elements for a particular sequence
* of schedule nodes.
*/
function schedule_options(nodes) {
return nodes.map(function(node) {
return $('<option>')
.attr('value', node.location)
.text(node.display_name)[0];
});
}
/**
* Return a schedule pruned by the given filter function.
*/
function pruned(tree, filter) {
return tree.filter(filter)
.map(function(node) {
var copy = {};
$.extend(copy, node);
if (node.children) copy.children = pruned(node.children, filter);
return copy;
})
.filter(function(node) {
return node.children === undefined || node.children.length;
});
}
/**
* Get table rows that represent the children of given row in the schedule
* tree.
*/
function get_children(row) {
var depth = $(row).data('depth');
return $(row).nextUntil(
$(row).siblings().filter(function() {
return $(this).data('depth') <= depth;
})
);
}
/**
* Handle click event for expanding/collapsing nodes in the schedule tree.
*/
function toggle_collapse(event) {
event.preventDefault();
var row = $(this).closest('tr');
var children = get_children(row);
if (row.is('.expanded')) {
$(this).removeClass('icon-caret-down').addClass('icon-caret-right');
row.removeClass('expanded').addClass('collapsed');
children.hide();
}
else {
$(this).removeClass('icon-caret-right').addClass('icon-caret-down');
row.removeClass('collapsed').addClass('expanded');
children.filter('.collapsed').each(function() {
children = children.not(get_children(this));
});
children.show();
}
}
/**
* Save changes.
*/
self.save = function() {
var button = $('#dirty-schedule #save-changes');
button.prop('disabled', true).text('${_("Saving")}...');
$.ajax({
url: save_url,
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(this.schedule),
success: function(data, textStatus, jqXHR) {
self.schedule = data.schedule;
self.dirty = false;
self.render();
button.prop('disabled', false).text('${_("Save changes")}');
// Update textarea with grading policy JSON, since grading policy
// may have changed.
$('#grading-policy').text(data.grading_policy);
},
error: function(jqXHR, textStatus, error) {
console.log(jqXHR.responseText);
$('#ajax-error').show();
$('#dirty-schedule').hide();
$('form#add-unit select,input,button').prop('disabled', true);
}
});
}
/**
* Add all nodes.
*/
$('#add-all').on('click', function(event) {
event.preventDefault();
schedule_apply(self.schedule, show);
self.dirty = true;
self.render();
});
/**
* Visit every tree node, applying function.
*/
function schedule_apply(nodes, f) {
nodes.map(function(node) {
f(node);
if (node.children !== undefined) schedule_apply(node.children, f);
});
}
/**
* Find a unit in the tree.
*/
function find_unit(tree, chapter, sequential, vertical) {
var units = find_lineage(tree, chapter, sequential, vertical);
return units[units.length -1];
}
function find_lineage(tree, chapter, sequential, vertical) {
function find_in(seq, location) {
for (var i = 0; i < seq.length; i++)
if (seq[i].location === location)
return seq[i];
}
var units = [],
unit = find_in(tree, chapter);
units[units.length] = unit;
if (sequential) {
units[units.length] = unit = find_in(unit.children, sequential);
if (vertical)
units[units.length] = unit = find_in(unit.children, vertical);
}
return units;
}
return self;
})();
$(function() { poc_schedule.render(); });
</script> </script>
<table class="poc-schedule">
<thead>
<tr>
<th><%- gettext('Unit') %></th>
<th><%- gettext('Start Date') %></th>
<th><%- gettext('Due Date') %></th>
<th><a href="#" id="remove-all">
<i class="icon-remove-sign icon"></i> <%- gettext('remove all') %>
</a></th>
</tr>
</thead>
<tbody>
<% _.each(chapters, function(chapter) { %>
<tr class="chapter collapsed" data-location="<%= chapter.location %>" data-depth="1">
<td class="unit">
<a href="#"><i class="icon-caret-right icon toggle-collapse"></i></a>
<%= chapter.display_name %>
</td>
<td class="date start-date"><a><%= chapter.start %></a></td>
<td class="date due-date"><a><%= chapter.due %></a></td>
<td><a href="#" class="remove-unit">
<i class="icon-remove-sign icon"></i> <%- gettext('remove') %>
</a></td>
</tr>
<% _.each(chapter.children, function(child) { %>
<tr class="sequential collapsed" data-depth="2"
data-location="<%= chapter.location %> <%= child.location %>">
<td class="unit">
<a href="#"><i class="icon-caret-right icon toggle-collapse"></i></a>
<%= child.display_name %>
</td>
<td class="date start-date"><a><%= child.start %></a></td>
<td class="date due-date"><a><%= child.due %></a></td>
<td><a href="#" class="remove-unit">
<i class="icon-remove-sign icon"></i> <%- gettext('remove') %>
</a></td>
</tr>
<% _.each(child.children, function(subchild) { %>
<tr class="vertical" data-dapth="3"
data-location="<%= chapter.location %> <%= child.location %> <%= subchild.location %>">
<td class="unit">&nbsp;<%= subchild.display_name %></td>
<td class="date start-date"><a><%= subchild.start %></a></td>
<td class="date due-date"><a><%= subchild.due %></a></td>
<td><a href="#" class="remove-unit">
<i class="icon-remove-sign icon"></i> <%- gettext('remove') %>
</a></td>
<% }); %>
<% }); %>
<% }); %>
</tbody>
</table>
...@@ -351,6 +351,8 @@ if settings.COURSEWARE_ENABLED: ...@@ -351,6 +351,8 @@ if settings.COURSEWARE_ENABLED:
'pocs.views.save_poc', name='save_poc'), 'pocs.views.save_poc', name='save_poc'),
url(r'^courses/{}/poc_invite$'.format(settings.COURSE_ID_PATTERN), url(r'^courses/{}/poc_invite$'.format(settings.COURSE_ID_PATTERN),
'pocs.views.poc_invite', name='poc_invite'), 'pocs.views.poc_invite', name='poc_invite'),
url(r'^courses/{}/poc_schedule$'.format(settings.COURSE_ID_PATTERN),
'pocs.views.poc_schedule', name='poc_schedule'),
url(r'^courses/{}/poc_manage_student$'.format(settings.COURSE_ID_PATTERN), url(r'^courses/{}/poc_manage_student$'.format(settings.COURSE_ID_PATTERN),
'pocs.views.poc_student_management', name='poc_manage_student'), 'pocs.views.poc_student_management', name='poc_manage_student'),
url(r'^courses/{}/poc_gradebook$'.format(settings.COURSE_ID_PATTERN), url(r'^courses/{}/poc_gradebook$'.format(settings.COURSE_ID_PATTERN),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment