Changeset - bc140e90c361
[Not reviewed]
default
0 12 0
Nathan Brink (binki) - 14 years ago 2011-10-26 23:44:20
ohnobinki@ohnopublishing.net
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.
12 files changed with 295 insertions and 40 deletions:
0 comments (0 inline, 0 general)
inc/class.schedule.php
Show inline comments
 
<?php /* -*- mode: php; -*- */
 
<?php /* -*- mode: php; indent-tabs-mode: nil; -*- */
 
/*
 
 * Copyright 2010 Nathan Gelderloos, Ethan Zonca, Nathan Phillip Brink
 
 *
 
 * This file is part of SlatePermutate.
 
 *
 
 * SlatePermutate is free software: you can redistribute it and/or modify
 
 * it under the terms of the GNU Affero General Public License as published by
 
 * the Free Software Foundation, either version 3 of the License, or
 
 * (at your option) any later version.
 
 *
 
 * SlatePermutate is distributed in the hope that it will be useful,
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 * GNU Affero General Public License for more details.
 
 *
 
 * You should have received a copy of the GNU Affero General Public License
 
 * along with SlatePermutate.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
$incdir = dirname(__FILE__) . DIRECTORY_SEPARATOR;
 
include_once $incdir . 'class.course.inc';
 
include_once $incdir . 'class.section.php';
 
include_once $incdir . 'class.page.php';
 
require_once $incdir . 'school.inc';
 
require_once $incdir . 'math.inc';
 

	
 
/*
 
 * Load a Classes -> Course converter class for the sake of the
 
 * Schedule::__wakeup() magic function.
 
 */
 
require_once $incdir . 'class.classes_convert.inc';
 

	
 
/**
 
 * \brief
 
 *   Finds possible Section combinations for a user's given Courses
 
 *   and stores and displays the results.
 
 *
 
 * Represents a schedule of a week. Stores the classes that are part
 
 * of that week and calculates all the possible permutations.
 
 */
 
class Schedule
 
{
 
  /*
 
   * Variables for upgrading from saved schedules created when there
 
   * was a class called Classes.
 
   */
 
  private $classStorage;			// array of courses
 
  private $nclasses;				// Integer number of classes
 

	
 
@@ -167,70 +168,75 @@ class Schedule
 
   *   Adds a new class to the schedule.
 
   *
 
   * \param $slot
 
   *   Currently, the Schedule class is not smart enough to understand
 
   *   CourseSlots. At a lower level, we split Courses with multiple
 
   *   CourseSlots into multiple Course objects with redundant
 
   *   information.
 
   */
 
  function addCourse($course_id, $title)
 
  {
 
    $this->courses[] = new Course($course_id, $title);
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Adds a section to this semester after finding the class.
 
   *
 
   * \param $instructor
 
   *   The instructor of this section/section_meeting.
 
   *
 
   * \return
 
   *   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'))
 
	&& (empty($slot) || !strcmp($slot, 'default')))
 
      return;
 

	
 
    /* reject invalid times */
 
    if (!strcmp($time_start, 'none') || !strcmp($time_end, 'none')
 
	|| $time_start > $time_end)
 
      {
 
	return 'Invalid time specifications for ' . htmlentities($course_name) . '-' . htmlentities($letter)
 
	  . '. Start time: <tt>' . htmlentities($time_start) . '</tt>. End time: <tt>' . htmlentities($time_end) . '</tt>.';
 
      }
 

	
 
    if (!empty($credit_hours) && !is_numeric($credit_hours))
 
      {
 
        return 'Invalid credit-hour specification of <tt>' . htmlentities($credit_hours) . '</tt> 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));
 

	
 
	  return;
 
	}
 

	
 
    error_log('Could not find class when parsing schedule from postData: ' . $course_name);
 
    echo 'Could not find class: ' . $course_name . "<br />\n";
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Get the school associated with this schedule.
 
   *
 
   * \return
 
   *   The school associated with this schedule or some fallback.
 
   */
 
  public function school_get()
 
  {
 
    $school = NULL;
 

	
 
    if (!empty($this->school_id))
 
      /*
 
@@ -508,49 +514,49 @@ class Schedule
 
	. '        <p class="centeredtext graytext"><em>Keyboard Shortcut: Left and right arrow keys switch between schedules</em></p>' . PHP_EOL;
 
    }		
 

	
 
    echo "\n";
 

	
 
    if($this->nPermutations > 0)
 
      {
 
	/*
 
	 * Figure out if we have to deal with Saturday and then deal
 
	 * with it.
 
	 *
 
	 * Also, ensure that our $time array is big enough for all of
 
	 * these courses.
 
	 */
 
	$max_day_plusone = 5;
 
	$have_saturday = FALSE;
 

	
 
	$max_time = (int)max($time);
 
	$min_time = (int)min($time);
 
	$sort_time = FALSE;
 
	foreach ($this->courses as $course)
 
	  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))
 
		    {
 
		      $max_day_plusone = 6;
 
		      $have_saturday = TRUE;
 
		    }
 

	
 
		  /* very late / very early classes */
 
		  while ((int)ltrim($meeting->getEndTime(), '0') > $max_time)
 
		    {
 
		      $max_time += $max_time + 30;
 
		      while ($max_time % 100 >= 60)
 
			$max_time += 40; /* + 100 - 60 */
 
		      $time[] = $max_time;
 
		    }
 
		  while ((int)ltrim($meeting->getStartTime(), '0') < $min_time)
 
		    {
 
		      $max_time += 30;
 
		      while ($min_time % 100 < 30)
 
			$min_time -= 40; /* + 60 - 100 */
 
		      $min_time -= 30;
 
		      $time[] = $min_time;
 
		      $sort_time = TRUE;
 
@@ -566,48 +572,49 @@ class Schedule
 
	  . '      <p class="regDialog-disclaimer graytext">' . PHP_EOL
 
	  . '        <em>' . PHP_EOL
 
	  . '          Note: The registration information above corresponds to the sections' . PHP_EOL
 
	  . '          displayed on the currently selected tab.' . PHP_EOL
 
	  . '        </em>' . PHP_EOL
 
	  . '      </p>' . PHP_EOL
 
	  . '      <p class="regDialog-disclaimer graytext">' . PHP_EOL
 
	  . '        <em>' . PHP_EOL
 
	  . '          Disclaimer: You are responsible for' . PHP_EOL
 
	  . '          double-checking the information you get from and input into slate_permutate' . PHP_EOL
 
	  . '          when registering for classes. There is no guarantee that the harvested' . PHP_EOL
 
	  . '          information is correct or that slate_permutate will handle' . PHP_EOL
 
	  . '          the information you enter correctly.' . PHP_EOL
 
	  . '        </em>' . PHP_EOL
 
	  . '      </p>' . PHP_EOL
 
	  . '    </div>' . PHP_EOL;
 
	echo '<div id="tabs">' . "\n" .
 
               '<div id="show-box" class="show-buttons">
 
                  <form action="#"><p class="nospace">
 
                    <label><strong>Display:</strong></label>
 
                    <input id="show-course-title" name="show-course-title" type="checkbox" /><label for="show-course-title">Course Title</label>
 
                    <input id="show-prof" name="show-prof" type="checkbox" checked="checked" /><label for="show-prof">Professor</label>
 
                    <input id="show-location" name="show-location" type="checkbox" /><label for="show-location">Room</label>
 
                    <input id="show-synonym" name="show-synonym" type="checkbox" /><label for="show-synonym">Synonym</label>
 
                    <input id="show-credit-hours" name="show-credit-hours" type="checkbox" /><label for="show-credit-hours">Credits</label>
 
                    <span id="regCodes"><label><a href="#"><strong>Register for Classes</strong></a></label></span></p>
 
                  </form>
 
                </div> <!-- id="show-box" -->'
 
	     . '<div id="the-tabs"><ul>' . "\n";
 

	
 
	$suppressed_permutations = array();
 
	if (!empty($_REQUEST['print']))
 
	  {
 
	    $print = $_REQUEST['print'];
 
	    if ($print !== 'all')
 
	      {
 
		for ($i = $first_permutation; $i <= $last_permutation; $i ++)
 
		  $suppressed_permutations[$i] = TRUE;
 
		foreach (explode(',', $print) as $item_to_print)
 
		  unset($suppressed_permutations[((int)$item_to_print) - 1]);
 
	      }
 
	  }
 

	
 
	for($nn = $first_permutation + 1; $nn <= $last_permutation; $nn++)
 
	  {
 
	    if (!empty($suppressed_permutations[$nn - 1]))
 
	      continue;
 
	    echo  "<li><a href=\"#tabs-" . $nn . "\">&nbsp;" . $nn . "&nbsp;</a></li>\n";
 
	  }
 
@@ -624,48 +631,55 @@ class Schedule
 
	  echo '      <div id="pager-next" class="pager right"><a href="' . htmlentities($this->my_url($page + 1)) . '">Next &raquo;</a></div>' . "\n";
 
	echo "    </div> <!-- id=\"pagers\" -->\n";
 

	
 

	
 
	echo "  <div class=\"scroller\">\n"
 
	  . "    <div class=\"scontent\">\n";
 
		
 
	for($i = $first_permutation; $i < $last_permutation; $i++)
 
	  {
 
	    /*
 
	     * Skip suppressed permutations, such as when displaying a
 
	     * page for printing a particular permutation.
 
	     */
 
	    if (!empty($suppressed_permutations[$i]))
 
	      continue;
 

	
 
	    /*
 
	     * Store a JSON list of courses, each with only the one
 
	     * section rendered in this permutation. This is used for
 
	     * the ``Registration Numbers'' dialog which noramlly
 
	     * shows users course synonyms.
 
	     */
 
	    $permutation_courses = array();
 

	
 
            /*
 
             * Count the number of credit hours in this particular
 
             * schedule.
 
             */
 
            $credit_hours = array();
 
            $have_credit_hours = FALSE;
 

	
 
	     echo  '      <div class="section" id="tabs-' . ($i+1) . "\">\n";
 
  
 
	    // Beginning of table
 
	    echo "        <table style=\"empty-cells:show;\" border=\"1\" cellspacing=\"0\">\n";
 
				
 
	    // Header row
 
	    echo "          <tr>\n"
 
	      . '            <td class="none permuteNum">' . ($i + 1) . "</td>\n"
 
	      . "            <td class=\"day\">Monday</td>\n"
 
	      . "            <td class=\"day\">Tuesday</td>\n"
 
	      . "            <td class=\"day\">Wednesday</td>\n"
 
	      . "            <td class=\"day\">Thursday</td>\n"
 
	      . "            <td class=\"day\">Friday</td>\n";
 
	    if ($have_saturday)
 
	      echo "            <td class=\"day\">Saturday</td>\n";
 
	    echo "          </tr>\n";
 

	
 
	    $last_meeting = array();
 
	    $rowspan = array(0, 0, 0, 0, 0, 0);
 
	    for($r = 0; $r < (count($time)-1); $r++)
 
	      {
 

	
 
		echo "          <tr>\n"
 
		  . "            <td class=\"time\">" . $this->prettyTime($time[$r]) . "</td>\n";
 
@@ -703,92 +717,108 @@ class Schedule
 
				    {
 
				      /* calculate how many rows this section should span */
 
				      for ($my_r = $r;
 
					   $my_r < (count($time)-1) && $current_meeting->getEndTime() > $time[$my_r];
 
					   $my_r ++)
 
					;
 
				      $rowspan[$dayLoop] = $my_r - $r;
 

	
 
				      $single_multi = 'single';
 
				      if ($rowspan[$dayLoop] > 1)
 
					$single_multi = 'multi';
 

	
 
				      $title = $course->title_get();
 
				      if (empty($title))
 
					$title = '';
 
				      else
 
					$title .= ' ';
 

	
 
				      $carret = '&#013;' . htmlentities("<br />");
 
				      echo '            <td rowspan="' . $rowspan[$dayLoop]
 
					. '" class="qTipCell ' . $single_multi . ' class' . $j
 
					. '" 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 '">'
 
					. '<span class="course-title block">' . htmlentities($title) . '</span>' . PHP_EOL
 
					. htmlentities($course->getName(), ENT_QUOTES) . '-'
 
					. htmlentities($section->getLetter(), ENT_QUOTES) . "\n"
 
					. '<span class="prof block">' . htmlentities($current_meeting->instructor_get(), ENT_QUOTES) . "</span>\n"
 
					. '<span class="location block">' . htmlentities($current_meeting->getLocation(), ENT_QUOTES) . "</span>\n"
 
					. '<span class="synonym block">' . htmlentities($section->getSynonym(), ENT_QUOTES) . "</span>\n"
 
                                        . '<span class="credit-hours block">' . htmlentities($section_credit_hours, ENT_QUOTES) . ' Credits</span>' . PHP_EOL
 
					. "</td>\n";
 

	
 
				      /* for the ``Registration Codes'' dialogue: */
 
				      if (empty($permutations_courses[$j]))
 
					{
 
					  $singleton_course = new Course($course->getName(), $course->title_get());
 
					  $singleton_course->section_add($section, $course_slot->id_get());
 
					  $permutation_courses[$j] = $singleton_course->to_json_array();
 
					}
 

	
 
				      $filled = TRUE;
 
				    }
 
			    } /* $course_slot */
 
			}
 
		    }
 

	
 
		  if ($rowspan[$dayLoop] > 0)
 
		    {
 
		      $filled = TRUE;
 
		      $rowspan[$dayLoop] --;
 
		    }
 

	
 
		  /* If the cell was not filled, fill it with an empty cell. */
 
			if(!$filled)
 
			{
 
				echo "            <td class=\"none\">&nbsp;</td>\n";
 
			}
 
			$filled = FALSE;
 
		}
 
		
 
		// End of row
 
		echo "          </tr>\n";
 
	      }
 

	
 
	    // End of table
 
	    echo "        </table>\n"
 
              . '         <span class="course-data">'.  htmlentities(json_encode($permutation_courses)) . "</span>\n"
 
	    echo "        </table>\n";
 

	
 
            if ($have_credit_hours)
 
              echo '        <p>Credit Hours: ' . sum($credit_hours) . '</p>' . PHP_EOL;
 

	
 
            echo ''
 
              . '        <span class="course-data">'.  htmlentities(json_encode($permutation_courses)) . "</span>\n"
 
	      . '      </div> <!-- id="section' . ($i + 1) . "\" -->\n";
 
	  }
 

	
 
          echo "    </div> <!-- class=\"scontent\" -->\n"
 
	     . "  </div> <!-- class=\"scroller\" -->\n"
 
	     . "</div> <!-- id=\"my-glider\" -->\n"
 
	     . $footcloser; // Closes off the content div
 
      } else {
 
      echo '<html><body><p>There are no possible schedules. Please <a href="input.php?s='.$this->id.'">try again</a>.</p></body></html>';
 
    }
 

	
 
    echo '<p id="possiblestats">' . PHP_EOL
 
      . '  There were a total of ' . $this->possiblePermutations . ' possible permutations. Only ' . $this->nPermutations . ' permutations had no class conflicts.' . PHP_EOL
 
      . '</p>' . PHP_EOL;
 
    if ($this->created)
 
      echo ''
 
	. '<p id="created-time">' . PHP_EOL
 
	. '  Created <span class="cute-time">' . gmdate('c', $this->created) . '</span>.' . PHP_EOL
 
	. '</p>' . PHP_EOL;
 

	
 
    $outputPage->foot();
 
  }
 

	
 
  //--------------------------------------------------
inc/class.section.php
Show inline comments
 
@@ -14,106 +14,124 @@
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 * GNU Affero General Public License for more details.
 
 *
 
 * You should have received a copy of the GNU Affero General Public License
 
 * along with SlatePermutate.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'class.section_meeting.inc';
 
   
 
/**
 
 * \brief
 
 *   Represent a Section associated with a Course.
 
 */
 
class Section
 
{
 

	
 
  private $letter;	// Section letter
 
  private $prof;	// Professor, preserved for Section::__wakeup()
 

	
 
  /* meeting times, array of SectionMeeting */
 
  private $meetings;
 

	
 
  /* 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
 
   *   Construct a Section.
 
   *
 
   * \param $letter
 
   *   The identifier (often a letter or numeral) of this section. For
 
   *   CS-262-A, this would be 'a'.
 
   * \param $section_meetings
 
   *   An array of SectionMeeting objects which describe all the
 
   *   different types of meetings this particular section has. It
 
   *   will be very uncommon for a course to have more than one such
 
   *   meeting time for a section. For example, Calvin doesn't have
 
   *   this. Another example, Cedarville lists different meeting times
 
   *   inside of a single section. Cedarville also lists all lectures
 
   *   and lab meeting times directly in a section's listing.
 
   * \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()
 
  {
 
    return $this->letter;
 
  }
 

	
 
  /**
 
   * \return
 
   *   This section's synonym -- a unique numeric identifier for this
 
   *   course. NULL if undefined.
 
   */
 
  public function getSynonym()
 
  {
 
    return $this->synonym;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Get an array of section meetings for this section.
 
   *
 
   * \return
 
   *   An array of SectionMeeting objects.
 
   */
 
  public function getMeetings()
 
  {
 
    return $this->meetings;
 
  }
 

	
 
  /**
 
   * \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
 
   *   The other section for which I should check for conflicts.
 
   * \return
 
   *   TRUE if there is a conflict, FALSE otherwise.
 
   */
 
  public function conflictsWith(Section $that)
 
  {
 
    foreach ($this->meetings as $this_meeting)
 
      foreach ($that->meetings as $that_meeting)
 
      if ($this_meeting->conflictsWith($that_meeting))
 
	return TRUE;
 

	
 
    return FALSE;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Add another section meeting time to this section.
 
   *
 
   * Useful for process.php when it's calling
 
   * Schedule::addSectionMeeting() multiple times.
 
   */
 
@@ -181,111 +199,119 @@ class Section
 
     * these amazingly.
 
     */
 
    if (!preg_match(';([a-zA-Z0-9]+);', $section_spec, $section_matches))
 
      return $ret;
 

	
 
    $ret['section'] = strtoupper($section_matches[1]);
 

	
 
    return $ret;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Get arrays of information needed by the AJAX stuff.
 
   *
 
   * \return
 
   *   An array of arrays that should be merged with the return value
 
   *   of other Section::to_json_arrays() calls.
 
   */
 
  public function to_json_arrays()
 
  {
 
    $json_arrays = array();
 

	
 
    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,
 
			    );
 

	
 
	$json_array += $meeting->to_json_array();
 
	$json_arrays[] = $json_array;
 
      }
 

	
 
    return $json_arrays;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Parse a set of JSON arrays into a Section.
 
   *
 
   * When this function was written, the JS frontend did not yet have
 
   * support for directly supporting sections +
 
   * section_meetings. Thus, multiple section meetings were simluated
 
   * by having multiple sections with the same section letter. Thus,
 
   * multiple ``sections'' of JSON are necessary to form together one
 
   * section.
 
   *
 
   * Thus, the caller must ensure that there is only one section in
 
   * the passed-in $json_arrays.
 
   *
 
   * \param $json_arrays
 
   *   The JSON array to be parsed into a Section.
 
   * \return
 
   *   A Section object.
 
   */
 
  public static function from_json_arrays(array $json_arrays)
 
  {
 
    $section_meetings = array();
 
    $letter = '';
 
    $synonym = NULL;
 
    foreach ($json_arrays as $meeting)
 
      {
 
	$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;
 
  }
 

	
 
  /* for legacy unserialization */
 
  private $start;
 
  private $tend;
 
  private $bdays;
 

	
 
  /**
 
   * \brief
 
   *   A magic function which tries to upgrade old serialized sections
 
   *   to the new format.
 
   */
 
  public function __wakeup()
 
  {
 
    /* upgrade to SectionMeeting stuffage */
 
    if (!empty($this->start))
 
      {
 
	$days = '';
 
	$daymap = array(0 => 'm', 1 => 't', 2 => 'w', 3 => 'h', 4 => 'f');
 
	foreach ($this->bdays as $day => $have_day)
 
	  if ($have_day)
 
	    $days .= $daymap[$day];
 

	
 
	/* the old format had a ->prof but initialied it to ``unknown prof'' */
 
	$this->prof = '';
 

	
 
	$this->meetings = array();
 
	$this->meeting_add(new SectionMeeting($days, $this->start, $this->tend, '', 'lecture', $this->prof));
 

	
 
	/*
 
	 * if we're reserialized in the future, make sure we don't do this same upgrade procedure again ;-).
 
	 */
 
	unset($this->start);
 
      }
 
    elseif (!empty($this->prof))
 
      {
 
	/* Move the instructor (old $this->prof) property to our SectionMeeting children */
 
	foreach ($this->meetings as $meeting)
 
	  $meeting->instructor_set($this->prof);
 
	unset($this->prof);
 
      }
 

	
 
    if (!isset($this->credit_hours))
 
      $this->credit_hours = -1.0;
 
  }
 
}
inc/class.semester.inc
Show inline comments
 
@@ -189,63 +189,66 @@ class Semester
 
   *   Add a section_meeting, calling Semester::section_add() as
 
   *   necessary.
 
   *
 
   * To be used by crawlers when parsing data which only presents one
 
   * section_meeting at a time. I.e., when they do tabular data right.
 
   *
 
   * \param $dept
 
   *   The department this section_meeting's course belongs to.
 
   * \param $course
 
   *   The course number this section_meeting's section belongs to.
 
   * \param $title
 
   *   The course title of the given course the section_meeting or
 
   *   NULL.
 
   *   belongs to.
 
   * \param $section
 
   *   The letter or numbers which make up the section's name.
 
   * \param $synonym
 
   *   The section synonym or NULL.
 
   * \param $section_meeting
 
   *   The SectionMeeting to be added to a section which may or may
 
   *   not already be in this 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);
 

	
 
    if (empty($this->departments[$dept][$course]))
 
      $course_obj = NULL;
 
    else
 
      {
 
	$course_obj = $this->departments[$dept][$course];
 
	$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;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Update the time_end.
 
   *
 
   * The time_end is a unix timestamp roughly estimating the time at
 
   * which a semester starts. It is used when guessing what semester a
 
   * user is interested in.
 
   *
 
   * \param $time_end
 
   *   The new time_end.
 
   */
 
  public function time_end_set($time_end)
 
  {
 
    $this->time_end = $time_end;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Set the time_end only if it would make the semester end later.
inc/math.inc
Show inline comments
 
@@ -26,48 +26,68 @@ if (!function_exists('mean'))
 
     *   overflowing stuff.
 
     */
 
    function mean(array $values)
 
    {
 
      /*
 
       * As the influence of each element reduces with each iteration
 
       * in the used algorithm, shuffling the array should give a
 
       * better idea of what the actual mean is for larger arrays.
 
       */
 
      shuffle($values);
 

	
 
      $val = 0;
 
      $i = 0;
 
      foreach ($values as $value)
 
        {
 
          $val = $val * $i / ($i + 1)
 
            + $value / ($i + 1);
 
          $i ++;
 
        }
 

	
 
      return $val;
 
    }
 
  }
 

	
 
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)
 
    {
 
      $mean = mean($values);
 

	
 
      $squares = 0;
 
      $i = 0;
 
      foreach ($values as $value)
 
        $squares += pow($mean - $value, 2);
 
      return sqrt($squares / (count($values) - 1));
 
    }
 
  }
 

	
 
/**
 
 * \brief
 
 *   Return the four quartile points of an array of sorted values with
 
 *   normal integral indexes.
 
 */
 
function sp_iqr(array $values)
 
{
 
  $count = count($values);
 
  if (!$count)
 
    return array(0, 0, 0, 0);
input.php
Show inline comments
 
@@ -132,49 +132,50 @@ if ($sch)
 
}
 
elseif ($errors_fix)
 
  {
 
    foreach ($_POST['postData'] as $course)
 
      if (is_array($course))
 
	{
 
	  $title = '';
 
	  if (!empty($course['title']))
 
	    $title = $course['title'];
 
	  if (empty($course['name']))
 
	    $my_hc .= '    class_last = add_class();' . PHP_EOL;
 
	  else
 
	    $my_hc .= '    class_last = add_class_n(' . json_encode($course['name']) . ', ' . json_encode($title) . ');' . PHP_EOL;
 
	  foreach ($course as $section)
 
	    if (is_array($section))
 
	      $my_hc .= '    add_section_n(class_last, ' . json_encode($section['letter']) . ', '
 
		. json_encode($section['synonym']) . ', ' . json_encode($section['start']) . ', '
 
		. json_encode($section['end']) . ', '
 
		. json_encode(array('m' => !empty($section['days'][0]), 't' => !empty($section['days'][1]), 'w' => !empty($section['days'][2]),
 
				    'h' => !empty($section['days'][3]), 'f' => !empty($section['days'][4]),
 
				    's' => !empty($section['days'][5])))
 
		. ', ' . 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;
 
	}
 
  }
 
else
 
  {
 
    $default_courses = school_default_courses($school);
 
    foreach ($default_courses as $default_class)
 
      $my_hc .= input_course_js($default_class, '    ');
 
  }
 
$my_hc .= '    class_last = add_class();' . PHP_EOL;
 
if ($qtips_always || !isset($_SESSION['saw_qtips']))
 
  {
 
    $my_hc .= '    addTips();' . PHP_EOL;
 
    $_SESSION['saw_qtips'] = TRUE;
 
  }
 
$my_hc .= '  });
 
';
 

	
 
$inputPage->headcode_add('scheduleInput', $inputPage->script_wrap($my_hc), TRUE);
 

	
 
$inputPage->head();
 

	
 
/*
 
 * Force a student to choose a school or declare he's a generic
 
@@ -293,69 +294,74 @@ if (!empty($_REQUEST['selectsemester']))
 
	  <col />
 
	  <col />
 
	</colgroup>
 
        <!-- Header -->
 
        <tr>
 
          <td>Class ID</td>
 
          <td class="center" id="letterNumber">Section</td>
 
          <td class="center">Prof</td>
 
          <td class="center">Start Time</td>
 
          <td class="center">End Time</td>
 
          <td class="center">M</td>
 
          <td class="center">Tu</td>
 
          <td class="center">W</td>
 
          <td class="center">Th</td>
 
          <td class="center">F</td>
 
	  <td class="center">S</td>
 
          <td class="center"></td>
 
          <td class="center"></td>
 
        </tr>
 
      </table>
 
    </td>
 
  </tr>
 
</table>
 

	
 
<div class="credit-hours-total">
 
  <p>Credit Hours: <span class="credit-hours-total-value">0</span></p>
 
</div>
 

	
 
<div class="paddingtop">
 
  <input class="button olive" type="submit" value="Find a schedule" />
 
</div>
 

	
 
</form>
 

	
 
<?php 
 

	
 
/* Show/hide Advanced Options: <p><span id="showadvanced" style="margin-left: 1em;"><a href="#">Advanced</a></span></p> */ 
 
?>
 
<div id="showInstructions" style="width: 100%; text-align: center;"><a href="#">Detailed Instructions...</a></div>
 

	
 
<?php
 
$inputPage->showSchoolInstructions();
 
$inputPage->foot();
 

	
 
function input_course_js(Course $course, $whitespace = '  ')
 
{
 
  $title = $course->title_get();
 
  if (empty($title))
 
    $title = '';
 
  $js = $whitespace . 'class_last = add_class_n(' . json_encode($course->getName()) . ', '
 
    . json_encode($title) . ');' . PHP_EOL;
 

	
 
  foreach ($course as $course_slot)
 
    foreach ($course_slot as $section)
 
      {
 
	$meetings = $section->getMeetings();
 
      foreach ($meetings as $meeting)
 
	{
 
	  $js .= $whitespace . 'add_section_n(class_last, ' . json_encode($section->getLetter()) . ', '
 
	    . json_encode($section->getSynonym()) . ', '
 
	    . json_encode($meeting->getStartTime()) . ', '
 
	    . json_encode($meeting->getEndTime()) . ', '
 
	    . json_encode(array('m' => $meeting->getDay(0), 't' => $meeting->getDay(1), 'w' => $meeting->getDay(2), 'h' => $meeting->getDay(3), 'f' => $meeting->getDay(4),
 
				's' => $meeting->getDay(5))) . ', '
 
	    . 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;
 
	}
 
    }
 

	
 
  return $js;
 
}
process.php
Show inline comments
 
@@ -181,58 +181,60 @@ if(!$DEBUG)
 
	    $semesters = school_semesters($school);
 
	    if (!empty($semesters[$postData['semester']]))
 
	      {
 
		$semester = $semesters[$postData['semester']];
 
		$page_create_options['semester'] = $semester;
 
	      }
 
	  }
 

	
 
	$allClasses = new Schedule($name, $parent_schedule_id, $school, $semester);
 

	
 
	$errors = array();
 
	foreach($postData as $course)
 
	  {
 
	    /*
 
	     * Only add classes if the user added at least one
 
	     * section to the class. We know that $course['name']
 
	     * is not a section, so count() needs to be > 1 and
 
	     * we need to skip over 'name' in our loop.
 
	     */
 
	    if(is_array($course) && count($course) > 1)
 
	      {
 
		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)
 
				  /* Skip the section name, which isn't a section */
 
					if(is_array($section))
 
					  {
 
					    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;
 
					  }
 
			}
 
		}
 

	
 
		/*
 
		 * Tell the user that his input is erroneous and
 
		 * require him to fix it.
 
		 */
 
		if (count($errors))
 
		  {
 
		    $error_page = page::page_create('Process Schedule — Errors', array('qTip2'), $page_create_options);
 
		    $error_page->head();
 

	
 
		    echo '        <p>' . PHP_EOL
 
		      . '          You have the following errors in your input:' . PHP_EOL
 
		      . '        </p>' . PHP_EOL
 
		      . '        <ul>' . PHP_EOL;
 
		    foreach ($errors as $error)
 
		      echo '          <li>' . $error . '</li>' . PHP_EOL;
 
		    echo '        </ul>' . PHP_EOL
 
		      . '        <h3>Solving Errors</h3>' . PHP_EOL
 
		      . '        <ul>' . PHP_EOL
school.d/calvin.crawl.inc
Show inline comments
 
@@ -355,49 +355,49 @@ function calvin_crawl_semester(array $sc
 
	      school_crawl_logf($school_crawl_log, 8, 'Unable to parse calvin section meeting info string into start/end/days information for '
 
				. implode('-', $section_id) . ': ``' . $sec_meeting_info . '\'\'');
 
	      $skipped_sections['invalid meeting info format'] ++;
 
	      /*
 
	       * Still add at least the course to the semester so that
 
	       * it shows up in autocmoplete.
 
	       */
 
	      calvin_crawl_course_add($semester, $section_id['department'], $section_id['course'], $title);
 
	      continue;
 
	    }
 
	  $date_start = $meeting_info_matches[1];
 
	  $date_end = $meeting_info_matches[2];
 
	  /* e.g., 'Lecture', 'Practicum' */
 
	  $meeting_type = school_crawl_meeting_type($meeting_info_matches[3]);
 

	
 
	  $days = school_crawl_days_format($school_crawl_log, explode(', ', $meeting_info_matches[5]));
 
	  $time_start = school_crawl_time_format(strptime($meeting_info_matches[6], '%I:%M%p'));
 
	  $time_end = school_crawl_time_format(strptime($meeting_info_matches[7], '%I:%M%p'));
 
	  $meeting_place = $meeting_info_matches[8];
 

	
 
	  foreach (array('date_start', 'date_end', 'meeting_type', 'days', 'time_start', 'time_end', 'meeting_place', 'meeting_type') as $var)
 
	    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
 
	   * school_semester_guess() function:
 
	   */
 
	  $date_start_time = strptime($date_start, '%m/%d/%Y');
 
	  $date_end_time = strptime($date_end, '%m/%d/%Y');
 

	
 
	  if ($date_start_time !== FALSE)
 
	    {
 
	      $date_start_time = school_crawl_gmmktime($date_start_time, -5 * 60*60);
 
	      $semester->time_start_pool_add($date_start_time);
 
	    }
 
	  if ($date_end_time !== FALSE)
 
	    {
 
	      $date_end_time = school_crawl_gmmktime($date_end_time, -5 * 60*60);
 
	      $semester->time_end_pool_add($date_end_time);
 
	    }
 
	}
 
	}
 

	
 
      if (!preg_match(';Page ([0-9]+) of ([0-9]+)\</td\>$;m', $html, $pages))
 
	{
 
	  school_crawl_logf($school_crawl_log, 0, 'Unable to determine the number of pages in this Calvin resultset');
school.d/cedarville.crawl.inc
Show inline comments
 
@@ -191,48 +191,49 @@ function cedarville_crawl_semester(array
 
	   * <meeting_info>: <type> <room> <days> <time_start>-<time_end> <meeting_info>
 
	   *
 
	   * It appears tht <type> may be:
 
	   * LEC: normal lecture meeting.
 
	   * ONL: online course.
 
	   * ILB: ethan says a partially online course...?
 
	   * HYB: hybrid of...?
 
	   * FLD: field...?
 
	   * FE2: ?
 
	   * CLN: ?
 
	   * LAB: Lab
 
	   * LES: something for some PFMU/PLMU class?
 
	   */
 

	
 
	  $synonym = $course_table[0]->nodeValue;
 
	  $section_parts = Section::parse($course_table[1]->nodeValue);
 
	  if (count($section_parts) < 3)
 
	    {
 
	      school_crawl_logf($school_crawl_log, 6, "Error parsing section_id. Given `%s'; interpreted as `%s'. Skipping.",
 
				$course_table[1]->nodeValue, implode('-', $section_parts));
 
	      continue;
 
	    }
 

	
 
          $title = $course_table[2]->nodeValue;
 
	  $credit_hours = $course_table[4]->nodeValue;
 

	
 
	  /*
 
	   * For courses with multiple section meetings, each
 
	   * instructor for each section meeting is separated by <br/>.
 
	   */
 
	  $instructors = array('');
 
	  foreach ($course_table[3]->childNodes as $child)
 
	    switch ($child->nodeType)
 
	      {
 
	      case XML_ELEMENT_NODE:
 
		end($instructors);
 
		if (!strcmp($child->tagName, 'br')
 
		    && strlen(trim($instructors[key($instructors)])))
 
		  $instructors[] = '';
 
		else
 
		  {
 
		    end($instructors);
 
		    $instructors[key($instructors)] .= $child->nodeValue;
 
		  }
 
		break;
 
	      case XML_TEXT_NODE:
 
		end($instructors);
 
		$instructors[key($instructors)] .= $child->data;
 
		break;
 
@@ -297,49 +298,49 @@ function cedarville_crawl_semester(array
 
		  if (!empty($date_start) && !empty($date_end))
 
		    {
 
		      $semester->time_start_set_test($date_start);
 
		      $semester->time_end_set_test($date_end);
 
		    }
 
		}
 

	
 
	      /*
 
	       * The tables are made for humans, not computers. If
 
	       * there aren't enough instructors for the number of
 
	       * section meetings, just reuse the first listed
 
	       * instructor:
 
	       */
 
	      if ($meeting_i >= count($instructors))
 
		$instructors[$meeting_i] = $instructors[0];
 

	
 
	      $meetings[] = new SectionMeeting($days, $time_start, $time_end,
 
					       $room, $type, $instructors[$meeting_i]);
 

	
 
	      $meeting_i ++;
 
	    }
 

	
 
	  $semester->section_add($section_parts['department'], $section_parts['course'],
 
				 new Section($section_parts['section'], $meetings,
 
					     $synonym), $title);
 
					     $synonym, $credit_hours), $title);
 
	}
 
    }
 

	
 
  return 0;
 
}
 

	
 
/**
 
 * \brief
 
 *   Look up the URI used to access information about a particular
 
 *   Cedarville semester.
 
 *
 
 * \param $semester
 
 *   The semester whose URI is being retrieved.
 
 * \param $document
 
 *   Optional DOMDocument of the Cedarville semester listing page, to
 
 *   aid seeding the cache. To prime the cache, just set $semester to
 
 *   NULL and pass in $document.
 
 * \return
 
 *   The URI for that semester's courses relative to
 
 *   CEDARVILLE_BASE_URI.
 
 */
 
function cedarville_semester_uri(Semester $semester = NULL, &$school_crawl_log, DOMDocument $document = NULL)
 
{
 
  static $semester_to_uri = array();
school.d/cedarville.inc
Show inline comments
 
@@ -39,50 +39,49 @@ function cedarville_instructions_html()
 
  SlatePermutate can be a useful tool for scheduling your next semester at <a href="http://cedarville.edu/" rel="external">Cedarville University</a>.
 
</p>
 
<ol>
 
  <li>Enter the course ID, such as PHYS-1020, in the Class ID blank. You will see a list of auto-suggestions.</li>
 
  <li><strong>You must click on the auto-suggested item</strong> to automatically add all sections of the class.</li>
 
  <li>Submit your schedule and view all of the different permutations of your schedule.</li>
 
  <li><strong>Schedule a meeting with your advisor to review your schedule.</strong></li>
 
  <li>When it's time to register, check the "Show Synonyms" box on your schedule and enter your course synonyms into the registration interface.</li>
 
</ol> <!--'-->
 
EOF;
 
}
 

	
 
/**
 
 * \brief
 
 *   Get a list of default classes (with sections (with meeting
 
 *   times)) for Cedarville students.
 
 *
 
 * \return
 
 *   An array of Course objects.
 
 */
 

	
 
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);
 
}
 

	
 
/**
 
 * \brief
 
 *   Implement <school_id>_page_css().
 
 */
 
function cedarville_page_css($school)
 
{
 
  return <<<CSS
 
#container .type-lab .sectionIdentifier,
 
#container .type-ilb .sectionIdentifier
 
{
 
  background: none !important;
 
}
 
#container .type-lab .sectionIdentifier input,
 
#container .type-ilb .sectionIdentifier input
 
{
 
  display: none;
 
}
 
#container .type-lab .deleteSection input,
 
#container .type-ilb .deleteSection
 
{
scripts/scheduleInput.js
Show inline comments
 
/* -*- tab-width: 4; -*-
 
 * Copyright 2010 Nathan Gelderloos, Ethan Zonca, Nathan Phillip Brink
 
 *
 
 * This file is part of SlatePermutate.
 
 *
 
 * SlatePermutate is free software: you can redistribute it and/or modify
 
 * it under the terms of the GNU Affero General Public License as published by
 
 * the Free Software Foundation, either version 3 of the License, or
 
 * (at your option) any later version.
 
 *
 
 * SlatePermutate is distributed in the hope that it will be useful,
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 * GNU Affero General Public License for more details.
 
 *
 
 * You should have received a copy of the GNU Affero General Public License
 
 * along with SlatePermutate.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
    //--------------------------------------------------
 
    // 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;
 

	
 
/**
 
 * \brief
 
 *   The number of section entries for a given course.
 
 *
 
 * Key is course_i, value is the current number of sections.
 
 */
 
var sectionsOfClass = new Array();
 

	
 
/**
 
 * \brief
 
 *   Help to generate a unique section identifier for each section
 
 *   added to a given course.
 
 *
 
 * Necessary to support PHP-style post array thingies, like
 
 * classes[0][1][$x] would be all of the data for course_i=0,
 
 * section_i=1, variable $x (ex. day of week, start time, end time,
 
 * teacher). We can't have two sections for a given course using the
 
 * same section_i because those values would override eachother.
 
 */
 
var last_section_i = 0;
 

	
 
/**
 
@@ -98,49 +106,49 @@ function addTips()
 
 /* slate_permutate_example_course_id is set globally in input.php. */
 
 jQuery('td:first', td).qtip(
 
   {
 
      content: 'Start typing your class ID (such as ' + slate_permutate_example_course_id + ') and click a suggestion to add sections',
 
      style: {
 
        tip: true,
 
        classes: "ui-tooltip-dark ui-tooltip-shadow ui-tooltip-rounded"
 
      },
 
      show: {
 
        ready: true
 
      },
 
      position:{
 
        my: 'top left', 
 
        at: 'bottom right'
 
      }
 
    }
 
  );
 

	
 
}
 

	
 
/**
 
 * \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);
 
    var last_tr;
 

	
 
	/*
 
	 * Add the type of the course to the CSS if it's a valid (and
 
	 * _clean-looking_ CSS class). Supports things like Cedarville's
 
	 * coloration of labs/ILB.
 
	 */
 
	if (/[a-z-]+/.exec(type) != null)
 
		cssclasses += ' type-' + type;
 

	
 
    var section_html = '<tr id="tr-section-' + String(snum) + '" class="' + cssclasses + '"><td class="none"></td>' +
 
	'<td class="sectionIdentifier center"><input type="text" size="1" class="required section-letter-entry" name="postData[' + cnum + '][' + snum + '][letter]" />' + 
 
	'  <input class="section-synonym-entry" type="hidden" name="postData[' + cnum + '][' + snum + '][synonym]" />' +
 
	'  <input class="section-slot-entry" type="hidden" name="postData[' + cnum + '][' + snum + '][slot]" />' +
 
	'</td>' +
 
	'<td class="professor center"><input type="text" size="10" class="profName" name="postData[' + cnum + ']['+ snum + '][professor]" /></td>' +
 
	'<td><select class="selectRequired" name="postData[' + cnum + '][' + snum + '][start]"><option value="none"></option>' +
 
	genOptionHtml("0700", "7:00 am", stime) + genOptionHtml("0730", "7:30 am", stime) +
 
	genOptionHtml("0800", "8:00 am", stime) + genOptionHtml("0830", "8:30 am", stime) +
 
	genOptionHtml("0900", "9:00 am", stime) + genOptionHtml("0930", "9:30 am", stime) +
 
	genOptionHtml("1000", "10:00 am", stime) + genOptionHtml("1030", "10:30 am", stime) +
 
@@ -181,136 +189,141 @@ function add_section_n(cnum, name, synon
 
	genOptionHtml("1820", "6:20 pm", etime) + genOptionHtml("1850", "6:50 pm", etime) +
 
	genOptionHtml("1920", "7:20 pm", etime) + genOptionHtml("1950", "7:50 pm", etime) +
 
	genOptionHtml("2020", "8:20 pm", etime) + genOptionHtml("2050", "8:50 pm", etime) +
 
	genOptionHtml("2120", "9:20 pm", etime) + genOptionHtml('2150', '9:50 pm', etime);
 

	
 
    if (etime.length > 0)
 
    {
 
	var etime_end = etime.substr(2);
 
	var etime_begin = etime.substr(0, 2);
 
	if (etime_end != '50' && etime_end != '20'
 
	   || etime_begin < 7 || etime_begin > 21)
 
	    section_html = section_html + genOptionHtml(etime, prettyTime(etime), etime);
 
    }
 

	
 
    section_html = section_html + '</select></td>\
 
<td class="cbrow"><input type="checkbox" class="daysRequired" name="postData[' + cnum + '][' + snum + '][days][0]" value="1" ' + (days.m ? 'checked="checked"' : '') + ' /></td>\
 
<td class="cbrow"><input type="checkbox" class="daysRequired" name="postData[' + cnum + '][' + snum + '][days][1]" value="1" ' + (days.t ? 'checked="checked"' : '') + ' /></td>\
 
<td class="cbrow"><input type="checkbox" class="daysRequired" name="postData[' + cnum + '][' + snum + '][days][2]" value="1" ' + (days.w ? 'checked="checked"' : '') + ' /></td>\
 
<td class="cbrow"><input type="checkbox" class="daysRequired" name="postData[' + cnum + '][' + snum + '][days][3]" value="1" ' + (days.h ? 'checked="checked"' : '') + ' /></td>\
 
<td class="cbrow"><input type="checkbox" class="daysRequired" name="postData[' + cnum + '][' + snum + '][days][4]" value="1" ' + (days.f ? 'checked="checked"' : '') + ' /></td>\
 
<td class="cbrow"><input type="checkbox" class="daysRequired" name="postData[' + cnum + '][' + snum + '][days][5]" value="1" ' + (days.s ? 'checked="checked"' : '') + ' /></td>' +
 
	'<td class="removeCell"><div class="deleteSection"><input type="button" value="x" class="gray" /></div></td><td class="emptyCell">' +
 
	'<input class="section-location-entry" type="hidden" name="postData[' + cnum + '][' + snum + '][location]" />' +
 
	'<input class="section-type-entry" type="hidden" name="postData[' + cnum + '][' + snum + '][type]" />' +
 
		'<input class="section-credit-hours-entry" type="hidden" name="postData[' + cnum + '][' + snum + '][credit_hours]" />' +
 
	'</td></tr>';
 

	
 
    /*
 
     * Try to append this section to the last section in its
 
     * associated CourseSlot...
 
     */
 
    last_tr = jQuery('tr.class' + cnum + '.' + safe_css_class('slot-' + slot) + ':last');
 
    if (!last_tr.length)
 
    {
 
	/* Also append a a new ``we are this slot'' row... */
 
	course_add_slot_row(cnum, slot);
 
	last_tr = jQuery('tr.class' + cnum + ':last');
 
    }
 
    last_tr.after(section_html);
 
    sectionsOfClass[cnum] ++;
 

	
 
    var section_tr = jQuery('#tr-section-' + String(snum));
 
    /* store course_i in a place the newly added section will look for it */
 
    section_tr.data({course_i: cnum});
 

	
 
    /*
 
     * Store data into the newly created HTML. With this method we
 
     * have to _avoid_ escaping entities in the text we're setting as
 
     * values because the DOM stuff will escape it for us.
 
     */
 
    section_tr.find('.section-letter-entry').val(name);
 
    section_tr.find('.section-synonym-entry').val(synonym);
 
    section_tr.find('.section-slot-entry').val(slot);
 
    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;
 
}
 

	
 
/**
 
 * Add a list of sections gotten via an AJAX call.
 
 */
 
function add_sections(cnum, data)
 
{
 
    var i;
 

	
 
    if (data.title)
 
	jQuery('.pclass' + cnum + ' .course-title-entry').val(data.title);
 

	
 
    /*
 
     * If the user enterred something iffy, correct him. Or do so
 
     * regardless ;-).
 
     */
 
    /* this data['class'] stuff is for the old JSON format we used... */
 
    if (data['class'])
 
	data.course = data['class'];
 
    if (data.course)
 
	jQuery('.className' + cnum).val(data.course);
 

	
 
    if (!data.sections)
 
	return;
 

	
 
    jQuery.each(data.sections, function(i, section)
 
		{
 
		    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);
 
		    });
 
}
 

	
 
/**
 
 * \brief
 
 *   Adds an identifier for a course slot.
 
 *
 
 * \param course_i
 
 *   The javascript index of the course to which a slot is being
 
 *   added.
 
 * \param slot_id
 
 *   The idenfifier of the slot.
 
 */
 
function course_add_slot_row(course_i, slot_id)
 
{
 
    var extra_classes = '';
 

	
 
    if (!show_course_slots)
 
    {
 
	var aclass;
 
	/*
 
	 * Then check if this course has multiple slots and we should
 
	 * enable displaying them to the user.
 
@@ -346,103 +359,104 @@ function enable_course_slots()
 
}
 

	
 
/**
 
 * \brief
 
 *   Adds a new class to the input.
 
 *
 
 * \param course_id
 
 *   The course_id.
 
 * \param title
 
 *   The human-friendly course title.
 
 * \return
 
 *   The javascript-local course entry identifying number.
 
 */
 
function add_class_n(course_id, title)
 
{
 
    /*
 
     * If we're adding a course entry form with preadded
 
     * content, first remove the empty course.
 
     */
 
    if (course_id.length && slate_permutate_course_free != -1)
 
	course_remove(slate_permutate_course_free);
 

	
 
    sectionsOfClass[classNum] = 0; // Initialize at 0
 
    course_ajax_requests[classNum] = false;
 
    jQuery('#jsrows').append('<tr id="tr-course-' + classNum + '" class="class class' + classNum + ' pclass' + classNum + '"><td class="nameTip"><input type="text" id="input-course-' + classNum + '" class="classRequired defText className'+classNum+' className" title="Class Name" name="postData[' + classNum + '][name]" /></td><td colspan="10"><input type="text" name="postData[' + classNum + '][title]" class="inPlace course-title-entry input-submit-disable" /></td><td class="tdInput"><div class="deleteClass"><input type="button" value="Remove" class="gray" /></div></td><td class="none"><button type="button" class="addSection gray">+</button></td></tr>');
 
    jQuery('#jsrows').append('<tr id="tr-course-' + classNum + '" class="class class' + classNum + ' pclass' + classNum + '"><td class="nameTip"><input type="text" id="input-course-' + classNum + '" class="classRequired defText className'+classNum+' className" title="Class Name" name="postData[' + classNum + '][name]" /></td><td colspan="10"><input type="text" name="postData[' + classNum + '][title]" class="inPlace inPlace-enable course-title-entry input-submit-disable" /><span class="course-credit-hours course-credit-hours-' + classNum + '"></span></td><td class="tdInput"><div class="deleteClass"><input type="button" value="Remove" class="gray" /></div></td><td class="none"><button type="button" class="addSection gray">+</button></td></tr>');
 

	
 
		/* store classNum as course_i into the <tr />: */
 
    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);
 

	
 
		class_elem.autocomplete({ source: 'auto.php?school=' + slate_permutate_school + '&semester=' + slate_permutate_semester });
 
		class_elem.bind('autocompleteselect', {class_num: classNum, class_elem: class_elem},
 
			function(event, ui)
 
			    {
 
				if (!ui.item)
 
				    return;
 

	
 
				if (ui.item.value.indexOf('-') != -1)
 
				    {
 
					course_autocomplete(event.data.class_num, ui.item.value);
 
				    }
 
				else
 
				    {
 
					/*
 
					 * The user selected a department, such as CS or MATH.
 
					 * Thus, we should append a '-' to the value and do a search for that.
 
					 */
 
					var newval = ui.item.value + '-';
 
					event.data.class_elem.
 
					    val(newval).
 
					    autocomplete("search", newval);
 

	
 
					/* void out the default event since we are setting the value ourselves, with a '-' */
 
					event.preventDefault();
 
				    }
 
			    });
 

	
 
		classNum++;
 

	
 
		return (classNum - 1);
 
	}
 

	
 
/**
 
 * \brief
 
 *   Ensure that there is an empty course entry and return its
 
 *   identifier.
 
 */
 
function add_class()
 
{
 
    /*
 
     * Don't add an empty new course entry if there already is
 
     * 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;
 
}
 

	
 
/**
 
 * \brief
 
 *   Try to fetch a section once the user has chosen an autocomplete
 
 *   entry.
 
 *
 
 * Since this can be called also when the user just types in a course
 
 * and hits enter without what he typed necessarily matching an
 
 * autocomplete item, this function handles the case where the
 
 * requested course might not have information on the server.
 
 *
 
 * \param course_i
 
 *   The javascript/postData index of the course to autocomplete.
 
 * \param term
 
 *   The term which the user entered. Optional.
 
 * \return
 
 *   Nothing.
 
 */
 
function course_autocomplete(course_i, term)
 
{
 
    var course_name_elem = jQuery('.className' + course_i);
 

	
 
@@ -505,48 +519,50 @@ function course_autocomplete(course_i, t
 

	
 
    return;
 
}
 

	
 
/**
 
 * \brief
 
 *   Remove a course entry.
 
 *
 
 * Ensures that slate_permutate_course_free is kept consistent.
 
 *
 
 * \param course_i
 
 *   The internal JS identifer for the course (not the course_id which
 
 *   the PHP cares about).
 
 */
 
function course_remove(course_i)
 
{
 
    jQuery('.class' + course_i).remove();
 

	
 
    /*
 
     * Check if the class intended for the user to
 
     * enter information into has been removed.
 
     */
 
    if (slate_permutate_course_free == course_i)
 
	slate_permutate_course_free = -1;
 

	
 
	credit_hours_change(course_i);
 
}
 

	
 
/**
 
 * \brief
 
 *   Figure whether or not a given course entry has sections.
 
 *
 
 * \param course_i
 
 *   The internal javascript representation of a course entry.
 
 * \return
 
 *   true or false.
 
 */
 
function course_has_sections(course_i)
 
{
 
    return sectionsOfClass[course_i] > 0;
 
}
 

	
 
/**
 
 * \brief
 
 *   Figure out whether or not an empty course entry has become filled
 
 *   or whether a full course has become emptied and react.
 
 *
 
 * This mainly ensures that there is always exactly one course entry
 
 * spot, eliminating the need of an ``Add class'' button.
 
 *
 
@@ -630,94 +646,216 @@ function prettyTime(time_str)
 
		i_hour -= 12;
 
	}
 
    hour_str = new String(i_hour);
 
    /* uncomment to have 08:01 instead of 8:01 */
 
    /*
 
    while (hour_str.length < 2)
 
	hour_str = '0' + hour_str;
 
    */
 

	
 
    return hour_str + ':' + time_str.substr(2) + ' ' + m + 'm';
 
}
 

	
 
/**
 
 * \brief
 
 *   Takes any value classname and tries to smooth it out to a valid
 
 *   CSS class name.
 
 *
 
 * \todo STUB
 
 */
 
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
 
//--------------------------------------------------
 
jQuery(document).ready(function() {
 

	
 
	//--------------------------------------------------
 
	// Deletes the selected class from input
 
	//--------------------------------------------------
 
	jQuery('.deleteClass').live('click', function() {
 
	    /* The user is not allowed to interactively delete the one empty course */
 
	    var course_i = jQuery(this).parent().parent().data('course_i');
 
	    if (slate_permutate_course_free == course_i)
 
		return false;
 
	    if(confirm('Delete class and all sections of this class?')) {
 
		/* The one empty course may have became this course in that time */
 
		if (slate_permutate_course_free == course_i)
 
		    return false;
 
		course_remove(course_i);
 
		return false;
 
	    }
 
	    return false;
 
	});
 

	
 
	//--------------------------------------------------
 
	// Deletes the selected section from the input
 
	//--------------------------------------------------
 
	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");
 

	
 
	  // The first input is the one containing the section ID
 
	  var toMatch = jQuery(row).find("input").val();
 
	    
 
	  // This gets the second class of the row, "class#"
 
	  var classClass = "." + jQuery(row).parent().attr("class").split(" ")[1];
 

	
 
	  // 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);
 
	});
 

	
 
	jQuery('.className').live('change', course_free_check).live('keyup', course_free_check);
 

	
 
	//--------------------------------------------------
 
	// Bind the section-adding method
 
	//--------------------------------------------------
 
	jQuery('.addSection').live('click', function() {
 
		var course_i = jQuery(this).parent().parent().data('course_i');
 
		add_section(course_i);
 
	});
 

	
 
	//--------------------------------------------------
 
	// Default text
 
	//--------------------------------------------------
 
	jQuery(".defText").focus(function(srcc)
 
	{
 
	    if (jQuery(this).val() == jQuery(this)[0].title)
 
	    {
 
		jQuery(this).removeClass("defaultTextActive");
 
		jQuery(this).val("");
 
	    }
 
@@ -749,46 +887,51 @@ jQuery(document).ready(function() {
 
		jQuery('#showInstructions').hide();
 
		jQuery('#schoolInstructionsBox').slideToggle();
 
		e.preventDefault();
 
	});
 

	
 

	
 
	//-------------------------------------------------
 
	// Show more saved schedules
 
	//-------------------------------------------------
 
        jQuery('#showMore').click( function() {
 
		jQuery('.hidden').show();
 
		jQuery('#showMore').hide();
 
		jQuery('#showLess').show();
 
        });
 
        jQuery('#showLess').click( function() {
 
		jQuery('.hidden').hide();
 
		jQuery('#showMore').show();
 
		jQuery('#showLess').hide();
 
	});
 

	
 

	
 
        //-------------------------------------------------
 
        // 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
 
     * title entry text fields.
 
     */
 
    jQuery('.input-submit-disable').live('keyup keydown', slate_permutate_nullify_enter);
 
    jQuery('.className').live('keyup keydown', function(e)
 
			      {
 
				  if (e.which == 13)
 
				  {
 
				      course_autocomplete(jQuery(this).parent().parent().data('course_i'));
 

	
 
				      /* Prevent form submission like slate_permutate_nullify_enter() does. */
 
				      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'));
 
	});
 
});
styles/general.css
Show inline comments
 
/*
 
 * Copyright 2010 Nathan Gelderloos, Ethan Zonca, Nathan Phillip Brink
 
 * Copyright 2011 Nathan Gelderloos, Ethan Zonca, Nathan Phillip Brink
 
 *
 
 * This file is part of SlatePermutate.
 
 *
 
 * SlatePermutate is free software: you can redistribute it and/or modify
 
 * it under the terms of the GNU Affero General Public License as published by
 
 * the Free Software Foundation, either version 3 of the License, or
 
 * (at your option) any later version.
 
 *
 
 * SlatePermutate is distributed in the hope that it will be useful,
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 * GNU Affero General Public License for more details.
 
 *
 
 * You should have received a copy of the GNU Affero General Public License
 
 * along with SlatePermutate.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
body {
 
  background: #fff;
 
}
 

	
 

	
 
/* Page Layout Styling */
 

	
 
@@ -105,48 +105,57 @@ a:link, a:visited, a:active {
 
  color: #777;
 
}
 

	
 
a:hover {
 
  color: #AAA;
 
}
 

	
 
#footer a:link, #footer a:visited, #footer a:active, #footer a:hover {
 
  color: #000;
 
  text-decoration: none;
 
}
 
#footer a:hover {
 
  text-decoration: underline;
 
}
 

	
 

	
 
/* Table Styling */
 

	
 
#container td {
 
  padding: .17em;
 
}
 
#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; 
 
}
 
#container td.center {
 
  text-align:center;
 
}
 
#container .section:nth-child(even) td {
 
  background: #EEE;
 
}
 
#container .section:nth-child(odd) td {
 
  background: #CCC;
 
}
 
#container .none {
 
  background: none!important; 
 
}
 
#container .emptyCell, .removeCell {
 
  background: none!important; 
 
}
 

	
 
/* Input Formatting */
 

	
 
#container {
 
  margin-left: 2em;
 
}
 
@@ -366,43 +375,58 @@ a:hover {
 
.button.large:hover { background-position: 0 -35px; }
 
.button.large:active { padding: 8px 12px 6px; background-position: 0 top; }
 

	
 
.smallurl {
 
  font-size: .9em;
 
}
 
.indent {
 
  margin-left: 1em;
 
}
 

	
 
/* qTip2 Styling */
 
.ui-tooltip-dark .ui-tooltip-content{
 
  border-color: #303030;
 
  border-width: 2px;
 
  color: #f3f3f3;
 
  background-color: #505050;
 

	
 
  background: rgba(80,80,80,.9)!important;
 

	
 
  font: normal bold 1.2em sans-serif;
 
}
 

	
 
.course-title-entry
 
{
 
    width: 80%;
 
    width: 70%;
 
}
 

	
 
.tr-slot-id-hidden
 
{
 
  display: none;
 
}
 

	
 
.inPlace {
 
  color: #000;
 
  border: none;
 
  border-color: transparent;
 
  background: transparent;
 
}
 

	
 
.warning
 
{
 
  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;
 
}
styles/output.css
Show inline comments
 
@@ -59,49 +59,50 @@
 
  border-bottom: 1px solid #DDD;
 
}
 
.single
 
{
 
  border-style:solid;
 
  background-color:#dddddd;
 
}
 
td{
 
  text-align:center;
 
  width:7em;
 
}
 
.time
 
{
 
  border-style:none none solid none; 
 
  border-bottom: 1px solid #aaa;
 
  white-space: nowrap;
 
}
 
.day{
 
  border-style:none none solid solid;
 
  background: #F0F0F0;
 
}
 

	
 
.prof,
 
.location,
 
.synonym
 
.synonym,
 
.credit-hours
 
{
 
    color: #444444;
 
    font-size: small;
 
}
 

	
 
/* Class Coloring */
 
.multi {  }
 
.class0 { background: #69c76f; }
 
.class1 { background: #c5c769; }
 
.class2 { background: #c76b69; }
 
.class3 { background: #696fc7; }
 
.class4 { background: #69a7c7; }
 
.class5 { background: #c769c6; }
 
.class6 { background: #989898; }
 
.class7 { background: #e8e8e8; }
 
.class8 { background: #111111; color: #fff; } 
 
.class9 { background: #00437d; color: #fff; }
 
.class10 { background: #7e2400; color: #fff; }
 
.permuteNum { border-bottom: 1px solid #000; font-weight: bold; color: #fff; background: #222; }
 

	
 
.top, .multi, .end {
 
  -webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1), -1px 2px 2px rgba(0,0,0,0.6);
 
     -moz-box-shadow:0 1px 1px rgba(0,0,0,0.3), -1px 1px 1px rgba(0,0,0,0.2);
 
          box-shadow:0 1px 1px rgba(0,0,0,0.3), -1px 1px 1px rgba(0,0,0,0.2);
0 comments (0 inline, 0 general)