# 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'])) +
+

Credit Hours: 0

+
+
@@ -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;