# HG changeset patch
# User Nathan Phillip Brink
# Date 2011-10-26 23:44:20
# Node ID bc140e90c3613d50e01b3c407b8fc70d6d2cc11a
# Parent 1bf6c5c1eb85c7d28d8b2ef054cd765ab228a87f
Crawl and store credit-hours per section. Display credit-hours, but provide no UI for updating them. Fixes bug #114.
Credit-hour crawling support for calvin and cedarville.
diff --git a/inc/class.schedule.php b/inc/class.schedule.php
--- a/inc/class.schedule.php
+++ b/inc/class.schedule.php
@@ -1,4 +1,4 @@
- Course converter class for the sake of the
@@ -188,7 +189,7 @@ class Schedule
* NULL on success, a string on error which is a message for the
* user and a valid XHTML fragment.
*/
- function addSection($course_name, $letter, $time_start, $time_end, $days, $synonym = NULL, $instructor = NULL, $location = NULL, $type = 'lecture', $slot = 'default')
+ function addSection($course_name, $letter, $time_start, $time_end, $days, $synonym = NULL, $instructor = NULL, $location = NULL, $type = 'lecture', $slot = 'default', $credit_hours = -1.0)
{
if (empty($letter) && (empty($time_start) || !strcmp($time_start, 'none')) && (empty($time_end) || !strcmp($time_end, 'none')) && empty($days)
&& empty($synonym) && empty($instructor) && empty($location) && (empty($type) || !strcmp($type, 'lecture'))
@@ -203,13 +204,18 @@ class Schedule
. '. Start time: ' . htmlentities($time_start) . '. End time: ' . htmlentities($time_end) . '.';
}
+ if (!empty($credit_hours) && !is_numeric($credit_hours))
+ {
+ return 'Invalid credit-hour specification of ' . htmlentities($credit_hours) . ' for ' . htmlentities($course_name) . '-' . htmlentities($letter) . '. Please use a floating point number or do not enter anything if the number of credit hours is not known.';
+ }
+
foreach ($this->courses as $course)
if (!strcmp($course_name, $course->getName()))
{
$section = $course->section_get($letter);
if (!$section)
{
- $section = new Section($letter, array(), $synonym);
+ $section = new Section($letter, array(), $synonym, $credit_hours);
$course->section_add($section, $slot);
}
$section->meeting_add(new SectionMeeting($days, $time_start, $time_end, $location, $type, $instructor));
@@ -529,7 +535,7 @@ class Schedule
foreach ($course as $course_slot)
{
for ($si = 0; $si < $course_slot->sections_count(); $si ++)
- foreach ($course_slot->section_get_i($si)->getMeetings() as $meeting)
+ foreach ($course_slot->section_get_i($si)->getMeetings() as $meeting)
{
/* Saturdayness */
if ($meeting->getDay(5))
@@ -587,6 +593,7 @@ class Schedule
+
'
@@ -645,6 +652,13 @@ class Schedule
*/
$permutation_courses = array();
+ /*
+ * Count the number of credit hours in this particular
+ * schedule.
+ */
+ $credit_hours = array();
+ $have_credit_hours = FALSE;
+
echo ' \n";
// Beginning of table
@@ -724,13 +738,24 @@ class Schedule
. '" title="' . htmlentities($title, ENT_QUOTES) . $carret
. 'Prof: ' . htmlentities($current_meeting->instructor_get(), ENT_QUOTES) . $carret
. 'Room: ' . htmlentities($current_meeting->getLocation(), ENT_QUOTES) . $carret
- . 'Type: ' . htmlentities($current_meeting->type_get(), ENT_QUOTES) . $carret . '">'
+ . 'Type: ' . htmlentities($current_meeting->type_get(), ENT_QUOTES) . $carret;
+
+ $section_credit_hours = $section->credit_hours_get();
+ if ($section_credit_hours >= 0)
+ {
+ $credit_hours[$section->getSynonym()] = $section_credit_hours;
+ $have_credit_hours = TRUE;
+
+ echo 'Credits: ' . htmlentities($section_credit_hours, ENT_QUOTES) . $carret;
+ }
+ echo '">'
. '
' . htmlentities($title) . '' . PHP_EOL
. htmlentities($course->getName(), ENT_QUOTES) . '-'
. htmlentities($section->getLetter(), ENT_QUOTES) . "\n"
. '
' . htmlentities($current_meeting->instructor_get(), ENT_QUOTES) . "\n"
. '
' . htmlentities($current_meeting->getLocation(), ENT_QUOTES) . "\n"
. '
' . htmlentities($section->getSynonym(), ENT_QUOTES) . "\n"
+ . '
' . htmlentities($section_credit_hours, ENT_QUOTES) . ' Credits' . PHP_EOL
. "\n";
/* for the ``Registration Codes'' dialogue: */
@@ -766,8 +791,13 @@ class Schedule
}
// End of table
- echo " \n"
- . '
'. htmlentities(json_encode($permutation_courses)) . "\n"
+ echo " \n";
+
+ if ($have_credit_hours)
+ echo '
Credit Hours: ' . sum($credit_hours) . '
' . PHP_EOL;
+
+ echo ''
+ . '
'. htmlentities(json_encode($permutation_courses)) . "\n"
. '
\n";
}
diff --git a/inc/class.section.php b/inc/class.section.php
--- a/inc/class.section.php
+++ b/inc/class.section.php
@@ -35,6 +35,11 @@ class Section
/* the section synonym which uniquely identifies this section/course combination */
private $synonym;
+ /**
+ * \brief
+ * The number of credit hours this course has.
+ */
+ private $credit_hours;
/**
* \brief
@@ -54,14 +59,15 @@ class Section
* \param $synonym
* Some schools have a unique number for each section. This field
* is for that number.
+ * \param $credit_hours
+ * The number of credit hours this course is worth.
*/
- function __construct ($letter, array $section_meetings = array(), $synonym = NULL)
+ function __construct ($letter, array $section_meetings = array(), $synonym = NULL, $credit_hours = -1.0)
{
$this->letter = $letter;
-
$this->meetings = $section_meetings;
-
$this->synonym = $synonym;
+ $this->credit_hours = (float)$credit_hours;
}
public function getLetter()
@@ -93,6 +99,18 @@ class Section
/**
* \brief
+ * Retrieve the number of credit hours this course has.
+ * \return
+ * The number of credit hours this course has, or a negative
+ * number if not specified.
+ */
+ public function credit_hours_get()
+ {
+ return $this->credit_hours;
+ }
+
+ /**
+ * \brief
* Check if this section conflicts with the given section.
*
* \param $that
@@ -202,7 +220,9 @@ class Section
foreach ($this->meetings as $meeting)
{
- $json_array = array('section' => $this->letter,
+ $json_array = array(
+ 'credit_hours' => $this->credit_hours_get(),
+ 'section' => $this->letter,
'synonym' => $this->synonym,
);
@@ -241,9 +261,12 @@ class Section
{
$letter = $meeting['section'];
$synonym = $meeting['synonym'];
+ if (!isset($json['credit_hours']) || $json['credit_hours'] < 0)
+ $json['credit_hours'] = -1.0;
+ $credit_hours = $json['credit_hours'];
$section_meetings[] = SectionMeeting::from_json_array($meeting);
}
- $section = new Section($letter, $section_meetings, $synonym);
+ $section = new Section($letter, $section_meetings, $synonym, $credit_hours);
return $section;
}
@@ -287,5 +310,8 @@ class Section
$meeting->instructor_set($this->prof);
unset($this->prof);
}
+
+ if (!isset($this->credit_hours))
+ $this->credit_hours = -1.0;
}
}
diff --git a/inc/class.semester.inc b/inc/class.semester.inc
--- a/inc/class.semester.inc
+++ b/inc/class.semester.inc
@@ -210,8 +210,11 @@ class Semester
* \param $course_slot_id
* The name of the new CourseSlot to create if the given section
* does not yet exist.
+ * \param $credit_hours
+ * The number of credit hours of the associated course or a
+ * negative value if unknown.
*/
- public function section_meeting_add($dept, $course, $title, $section, $synonym, $section_meeting, $course_slot_id = 'default')
+ public function section_meeting_add($dept, $course, $title, $section, $synonym, $section_meeting, $course_slot_id = 'default', $credit_hours = -1.0)
{
$dept = strtoupper($dept);
$course = strtoupper($course);
@@ -224,7 +227,7 @@ class Semester
$section_obj = $course_obj->section_get($section);
}
if (empty($course_obj) || empty($section_obj))
- return $this->section_add($dept, $course, new Section($section, array($section_meeting), $synonym), $title, $course_slot_id);
+ return $this->section_add($dept, $course, new Section($section, array($section_meeting), $synonym, $credit_hours), $title, $course_slot_id);
$section_obj->meeting_add($section_meeting);
return;
diff --git a/inc/math.inc b/inc/math.inc
--- a/inc/math.inc
+++ b/inc/math.inc
@@ -47,6 +47,26 @@ if (!function_exists('mean'))
}
}
+if (!function_exists('sum'))
+ {
+ /**
+ * \brief
+ * Add all elements in a set together.
+ *
+ * \parram $S
+ * The set to sum up.
+ * \return
+ * The sum of all elements in the set.
+ */
+ function sum($S)
+ {
+ $ret = 0;
+ foreach ($S as $S_i)
+ $ret += $S_i;
+ return $ret;
+ }
+ }
+
if (!function_exists('stddev'))
{
function stddev(array $values)
diff --git a/input.php b/input.php
--- a/input.php
+++ b/input.php
@@ -153,7 +153,8 @@ elseif ($errors_fix)
. ', ' . json_encode($section['professor']) . ', '
. json_encode($section['location']) . ', '
. json_encode($section['type']) . ', '
- . json_encode($section['slot']) . ');' . PHP_EOL;
+ . json_encode($section['slot']) . ', '
+ . json_encode(isset($section['credit_hours']) ? $section['credit_hours'] : -1) . ');' . PHP_EOL;
$my_hc .= PHP_EOL;
}
}
@@ -314,6 +315,10 @@ if (!empty($_REQUEST['selectsemester']))
+
+
@@ -353,7 +358,8 @@ function input_course_js(Course $course,
. json_encode($meeting->instructor_get()) . ', '
. json_encode($meeting->getLocation()) . ', '
. json_encode($meeting->type_get()) . ', '
- . json_encode($course_slot->id_get()) . ');' . PHP_EOL;
+ . json_encode($course_slot->id_get()) . ', '
+ . json_encode($section->credit_hours_get()) . ');' . PHP_EOL;
}
}
diff --git a/process.php b/process.php
--- a/process.php
+++ b/process.php
@@ -202,6 +202,8 @@ if(!$DEBUG)
if (empty($course['title']))
$course['title'] = '';
+ if (empty($course['credit_hours']))
+ $course['credit_hours'] = -1;
$allClasses->addCourse($course['name'], $course['title']);
foreach($course as $section)
@@ -211,7 +213,7 @@ if(!$DEBUG)
if (empty($section['slot']))
$section['slot'] = 'default';
- $error_string = $allClasses->addSection($course['name'], $section['letter'], $section['start'], $section['end'], arrayToDays(empty($section['days']) ? array() : $section['days'], 'alpha'), $section['synonym'], $section['professor'], $section['location'], $section['type'], $section['slot']);
+ $error_string = $allClasses->addSection($course['name'], $section['letter'], $section['start'], $section['end'], arrayToDays(empty($section['days']) ? array() : $section['days'], 'alpha'), $section['synonym'], $section['professor'], $section['location'], $section['type'], $section['slot'], $section['credit_hours']);
if ($error_string !== NULL)
$errors[] = $error_string;
}
diff --git a/school.d/calvin.crawl.inc b/school.d/calvin.crawl.inc
--- a/school.d/calvin.crawl.inc
+++ b/school.d/calvin.crawl.inc
@@ -376,7 +376,7 @@ function calvin_crawl_semester(array $sc
school_crawl_logf($school_crawl_log, 10, "%s:%s", $var, ${$var});
$semester->section_meeting_add($section_id['department'], $section_id['course'], $title, $section_id['section'], $synonym,
- new SectionMeeting($days, $time_start, $time_end, $meeting_place, $meeting_type, $faculty_name));
+ new SectionMeeting($days, $time_start, $time_end, $meeting_place, $meeting_type, $faculty_name), 'default', $credits);
/*
* Try to update semester's longetivity stats to help the
diff --git a/school.d/cedarville.crawl.inc b/school.d/cedarville.crawl.inc
--- a/school.d/cedarville.crawl.inc
+++ b/school.d/cedarville.crawl.inc
@@ -212,6 +212,7 @@ function cedarville_crawl_semester(array
}
$title = $course_table[2]->nodeValue;
+ $credit_hours = $course_table[4]->nodeValue;
/*
* For courses with multiple section meetings, each
@@ -318,7 +319,7 @@ function cedarville_crawl_semester(array
$semester->section_add($section_parts['department'], $section_parts['course'],
new Section($section_parts['section'], $meetings,
- $synonym), $title);
+ $synonym, $credit_hours), $title);
}
}
diff --git a/school.d/cedarville.inc b/school.d/cedarville.inc
--- a/school.d/cedarville.inc
+++ b/school.d/cedarville.inc
@@ -60,8 +60,7 @@ EOF;
function cedarville_default_courses()
{
$chapel = new Course('Chapel','Chapel');
- $chapel->section_add(new Section('_', array(new SectionMeeting('mtwhf', '1000', '1045', '', 'chapel','n/a')),
- '', '_'));
+ $chapel->section_add(new Section('_', array(new SectionMeeting('mtwhf', '1000', '1045', '', 'chapel','n/a'))));
return array($chapel);
}
diff --git a/scripts/scheduleInput.js b/scripts/scheduleInput.js
--- a/scripts/scheduleInput.js
+++ b/scripts/scheduleInput.js
@@ -21,6 +21,14 @@
// General Notes
//--------------------------------------------------
+/**
+ * \brief
+ * The next course_i value that will be produced when add_class() is
+ * called.
+ *
+ * If iterating through all of the possible courses, use (classNum -
+ * 1) as the upper bound.
+ */
var classNum = 0;
/**
@@ -119,7 +127,7 @@ function addTips()
* \brief
* Add a section to a class.
*/
-function add_section_n(cnum, name, synonym, stime, etime, days, instructor, location, type, slot)
+function add_section_n(cnum, name, synonym, stime, etime, days, instructor, location, type, slot, credit_hours)
{
var snum = last_section_i ++;
var cssclasses = 'section class' + cnum + ' ' + safe_css_class('slot-' + slot);
@@ -202,6 +210,7 @@ function add_section_n(cnum, name, synon
' | ' +
'' +
'' +
+ '' +
' | ';
/*
@@ -233,16 +242,19 @@ function add_section_n(cnum, name, synon
section_tr.find('.profName').val(instructor);
section_tr.find('.section-location-entry').val(location);
section_tr.find('.section-type-entry').val(type);
+ section_tr.find('.section-credit-hours-entry').val(credit_hours);
/* unhide the saturday columns if it's used by autocomplete data */
if (days.s)
jQuery('#jsrows col.saturday').removeClass('collapsed');
+ credit_hours_change(cnum);
+
return last_section_i - 1;
}
function add_section(cnum)
{
- var section_i = add_section_n(cnum, '', '', '', '', {m: false, t: false, w: false, h: false, f: false, s: false}, '', '', '', 'default');
+ var section_i = add_section_n(cnum, '', '', '', '', {m: false, t: false, w: false, h: false, f: false, s: false}, '', '', '', 'default', -1);
if (cnum == slate_permutate_course_free)
course_free_check(cnum);
return section_i;
@@ -275,21 +287,22 @@ function add_sections(cnum, data)
{
if (!section.slot)
section.slot = 'default';
+ if (section.credit_hours === undefined)
+ section.credit_hours = -1;
- add_section_n(cnum, section.section, section.synonym, section.time_start, section.time_end, section.days, section.instructor, section.location, section.type, section.slot);
+ add_section_n(cnum, section.section, section.synonym, section.time_start, section.time_end, section.days, section.instructor, section.location, section.type, section.slot, section.credit_hours);
});
/*
* Handle course-level interdependencies.
*/
if (data.dependencies)
- jQuery.each(data.dependencies, function(i, dep)
- {
+ jQuery.each(data.dependencies, function(i, dep) {
/* Gracefully deprecate the old crawler's JSON format. */
if (dep['class'])
dep.course = dep['class'];
- var new_course_num = add_class_n(dep.course, dep['title'] ? dep['title'] : '');
+ var new_course_num = add_class_n(dep.course, dep['title'] ? dep['title'] : '');
add_sections(new_course_num, dep);
});
}
@@ -367,13 +380,14 @@ function add_class_n(course_id, title)
sectionsOfClass[classNum] = 0; // Initialize at 0
course_ajax_requests[classNum] = false;
- jQuery('#jsrows').append(' | | | |
');
+ jQuery('#jsrows').append(' | | | |
');
/* store classNum as course_i into the
: */
var tr_course = jQuery('#tr-course-' + classNum);
tr_course.data({course_i: classNum});
tr_course.find('.course-title-entry').val(title);
tr_course.find('.className').val(course_id);
+ tr_course.find('.course-credit-hours-label').attr('for', 'course-credit-hours-entry-' + classNum);
var class_elem = jQuery('.className' + classNum);
@@ -421,7 +435,7 @@ function add_class()
* one. Otherwise, set this new class to be the ``hot'' one.
*/
if (slate_permutate_course_free == -1)
- slate_permutate_course_free = add_class_n('', '');
+ slate_permutate_course_free = add_class_n('', '');
return slate_permutate_course_free;
}
@@ -526,6 +540,8 @@ function course_remove(course_i)
*/
if (slate_permutate_course_free == course_i)
slate_permutate_course_free = -1;
+
+ credit_hours_change(course_i);
}
/**
@@ -651,6 +667,128 @@ function safe_css_class(classname)
return classname;
}
+/**
+ * \internal
+ * \brief
+ * Whether or not to display the credit_hours column is currently
+ * being displayed to the user.
+ *
+ * An internal state variable for show_credit_hours().
+ */
+var credit_hours_shown = false;
+
+/**
+ * \brief
+ * Display the Credit Hours column to the user.
+ */
+function show_credit_hours()
+{
+ if (credit_hours_shown)
+ return;
+
+ jQuery('#content').addClass('credit-hours-shown');
+
+ credit_hours_shown = true;
+}
+
+/**
+ * \brief
+ * Hide the Credit Hours column from the user.
+ */
+function hide_credit_hours()
+{
+ if (!credit_hours_shown)
+ return;
+
+ jQuery('#content').removeClass('credit-hours-shown');
+
+ credit_hours_shown = false;
+}
+
+/**
+ * \brief
+ * State for the displification of the total number of credit hours.
+ */
+var credit_hours = [];
+
+/**
+ * \brief
+ * Update the running credit hours total.
+ */
+function credit_hours_change(course_i)
+{
+ var objs = jQuery('.section.class' + course_i + ' .section-credit-hours-entry');
+
+ if (objs.length)
+ {
+ var course_credit_hours = {min: -1, max: -1};
+
+ objs.each(function(i, e) {
+ var obj = jQuery(e);
+ var section = obj.closest('.section').find('.section-letter-entry').val();
+
+ var val = obj.val();
+ if (!val.length)
+ return true;
+ var hours = parseFloat(val);
+ if (!isNaN(hours) && hours >= 0)
+ {
+ if (hours > course_credit_hours.max)
+ course_credit_hours.max = hours;
+ if (course_credit_hours.min < 0 || hours < course_credit_hours.min)
+ course_credit_hours.min = hours;
+ }
+ });
+ credit_hours[course_i] = course_credit_hours;
+
+ if (course_credit_hours.min >= 0)
+ {
+ var text = course_credit_hours.min;
+ if (course_credit_hours.max != course_credit_hours.min)
+ text += '-' + course_credit_hours.max;
+ text += ' Credits';
+ jQuery('#tr-course-' + course_i + ' .course-credit-hours').text(text);
+ }
+ }
+ else
+ /* course_i was deleted or is void */
+ credit_hours[course_i] = {min: -1, max: -1};
+
+ var credit_hours_total = {min: 0, max: 0};
+ var saw_credit_hours = false;
+ var course_j;
+ for (course_j = 0; course_j < classNum; course_j ++)
+ {
+ if (credit_hours[course_j] === undefined)
+ continue;
+
+ /* Ignore deleted courses */
+ if (credit_hours[course_j] && !jQuery('tr.class' + course_j).length)
+ {
+ credit_hours[course_j] = undefined;
+ continue;
+ }
+
+ /* Ignore courses which have no credit_hours set. */
+ if (credit_hours[course_j].min < 0)
+ continue;
+ saw_credit_hours = true;
+
+ credit_hours_total.min += credit_hours[course_j].min;
+ credit_hours_total.max += credit_hours[course_j].max;
+ }
+
+ if (saw_credit_hours)
+ show_credit_hours();
+ else
+ hide_credit_hours();
+
+ var text = credit_hours_total.min;
+ if (credit_hours_total.max != credit_hours_total.min)
+ text += '-' + credit_hours_total.max;
+ jQuery('.credit-hours-total-value').text(text);
+}
+
//--------------------------------------------------
// Items bound to pageload/events
//--------------------------------------------------
@@ -680,7 +818,6 @@ jQuery(document).ready(function() {
jQuery('.deleteSection').live('click', function() {
// Decreases the total number of classes
var course_i = jQuery(this).parent().parent().data('course_i');
- sectionsOfClass[course_i]--;
// Find the ID cell of the row we're in
var row = jQuery(this).parent().parent().find(".sectionIdentifier");
@@ -694,9 +831,10 @@ jQuery(document).ready(function() {
// Iterate over each section of this class
jQuery(classClass).each( function() {
// If this section has the same course ID as the item clicked, remove it.
- if(jQuery(this).find("input").val() == toMatch){
- jQuery(this).remove();
- }
+ if(jQuery(this).find("input").val() == toMatch) {
+ jQuery(this).remove();
+ sectionsOfClass[course_i]--;
+ }
});
course_free_check(course_i);
});
@@ -770,8 +908,8 @@ jQuery(document).ready(function() {
//-------------------------------------------------
// Style course titles as inputs when clicked
//-------------------------------------------------
- jQuery('.course-title-entry').live('click', function() {
- jQuery(this).toggleClass('inPlace');
+ jQuery('.inPlace-enable').live('click', function() {
+ jQuery(this).removeClass('inPlace');
});
/*
* Prevent accidental form submission for className and course
@@ -788,7 +926,12 @@ jQuery(document).ready(function() {
return false;
}
});
- jQuery('.course-title-entry').live('blur', function() {
+ jQuery('.inPlace-enable').live('blur', function() {
jQuery(this).addClass('inPlace');
});
+
+ credit_hours_shown = jQuery('#content').is('.credit-hours-shown');
+ jQuery('.section-credit-hours-entry').live('change', function() {
+ credit_hours_change(jQuery(this).closest('.section').data('course_i'));
+ });
});
diff --git a/styles/general.css b/styles/general.css
--- a/styles/general.css
+++ b/styles/general.css
@@ -1,5 +1,5 @@
/*
- * Copyright 2010 Nathan Gelderloos, Ethan Zonca, Nathan Phillip Brink
+ * Copyright 2011 Nathan Gelderloos, Ethan Zonca, Nathan Phillip Brink
*
* This file is part of SlatePermutate.
*
@@ -126,6 +126,15 @@ a:hover {
#container .class td {
background: #70a97c;
}
+#container .class input
+{
+ display: inline-block;
+}
+#container .class .course-credit-hours
+{
+ width: 20%;
+ text-align: right;
+}
#container .tdInput {
background: none!important;
}
@@ -387,7 +396,7 @@ a:hover {
.course-title-entry
{
- width: 80%;
+ width: 70%;
}
.tr-slot-id-hidden
@@ -397,7 +406,7 @@ a:hover {
.inPlace {
color: #000;
- border: none;
+ border-color: transparent;
background: transparent;
}
@@ -406,3 +415,18 @@ a:hover {
background: #ffffdd;
border: 1pt solid yellow;
}
+
+#content .course-credit-hours,
+#content .credit-hours-total
+{
+ display: none;
+}
+
+#content.credit-hours-shown .course-credit-hours
+{
+ display: inline-block;
+}
+#content.credit-hours-shown .credit-hours-total
+{
+ display: block;
+}
diff --git a/styles/output.css b/styles/output.css
--- a/styles/output.css
+++ b/styles/output.css
@@ -80,7 +80,8 @@ td{
.prof,
.location,
-.synonym
+.synonym,
+.credit-hours
{
color: #444444;
font-size: small;