Changeset - 9fd95ed08e56
[Not reviewed]
default
0 8 1
Nathan Brink (binki) - 15 years ago 2011-03-22 16:23:52
ohnobinki@ohnopublishing.net
Load the course registration codes dialogue from an AJAX callback. Allow individual schools to override the content of the Registration Codes dialogue.
9 files changed with 351 insertions and 21 deletions:
0 comments (0 inline, 0 general)
ajax.php
Show inline comments
 
new file 100644
 
<?php
 
/*
 
 * Copyright 2011 Nathan Phillip Brink <ohnobinki@ohnopublishing.net>
 
 *
 
 * This file is a part of slate_permutate.
 
 *
 
 * slate_permutate 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.
 
 *
 
 * slate_permutate 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 slate_permutate.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/**
 
 * \file
 
 *   This file is an endpoint for generic AJAX requests (as opposed to
 
 *   autocompletion of course names).
 
 */
 

	
 
require_once('inc/school.inc');
 
require_once('inc/class.page.php');
 
require_once('inc/class.course.inc');
 

	
 
page::session_start();
 

	
 
/* should the following block of code be moved into a proposed Page::json()? */
 
if (isset($_REQUEST['txt'])) {
 
  header('Content-Type: text/plain; encoding=utf-8');
 
}
 
else {
 
  header('Content-Type: application/json; encoding=utf-8');
 
}
 

	
 
/**
 
 * \brief
 
 *   Wrap an error message up in JSON and send it.
 
 *
 
 * \param $message
 
 *   A valid XHTML fragment which will be wrapped in <div class="error" />
 
 */
 
function slate_permutate_json_error($message)
 
{
 
  echo json_encode(array('success' => FALSE, 'message' => '<div class="error">' . $message . '</div>'));
 
  exit;
 
}
 

	
 
/**
 
 * \brief
 
 *   Send a successful JSON response.
 
 *
 
 * \param $data
 
 *   A PHP array to be encoded with json_encode() and sent as
 
 *   obj.data.
 
 */
 
function slate_permutate_json_success(array $data = array())
 
{
 
  echo json_encode(array('success' => TRUE, 'data' => $data));
 
  exit;
 
}
 

	
 
if (isset($_REQUEST['school_registration_html']))
 
  {
 
    if (!empty($_REQUEST['school']))
 
      $school = school_load($_REQUEST['school']);
 
    if (empty($school))
 
      {
 
	$school = school_load_guess();
 
	if (empty($school))
 
	  slate_permutate_json_error('Unable to load any school.');
 
      }
 

	
 
    $page = new Page('', array(), FALSE);
 

	
 
    $courses = array();
 
    if (!empty($_REQUEST['courses']) && is_array($_REQUEST['courses']))
 
      {
 
	/*
 
	 * The below course deserialization blob should be moved into
 
	 * the Course object.
 
	 */
 
	foreach ($_REQUEST['courses'] as $course_json)
 
	  {
 
	    $course = Course::from_json_array($course_json);
 
	    if (!empty($course))
 
	      $courses[] = $course;
 
	  }
 
      }
 

	
 
    $html = school_registration_html($page, $school, $courses);
 
    if (empty($html))
 
      slate_permutate_json_error('School\'s registration information producer returned no data.');
 
    slate_permutate_json_success(array('html' => $html));
 
  }
 

	
 
slate_permutate_json_error('Unrecognized command.');
inc/class.course.inc
Show inline comments
 
@@ -26,7 +26,7 @@
 

	
 
include_once 'class.section.php';
 

	
 
class Course
 
class Course implements IteratorAggregate
 
{
 
  private $name;	// String
 
  private $sections;	// Array of sections
 
@@ -65,6 +65,15 @@ class Course
 

	
 
  /**
 
   * \brief
 
   *   Required function to implement the IteratorAggregate interface.
 
   */
 
  public function getIterator()
 
  {
 
    return new ArrayIterator($this->sections);
 
  }
 

	
 
  /**
 
   * \brief
 
   *    Returns the number of sections in the class.
 
   */
 
  function getnsections()
 
@@ -202,6 +211,30 @@ class Course
 

	
 
  /**
 
   * \brief
 
   *   Produce a Course object based on a JSON array compatible with
 
   *   the output of Course::to_json_array().
 
   *
 
   * \param $json
 
   *   The JSON array to parse.
 
   * \return
 
   *   A Course.
 
   */
 
  public static function from_json_array($json)
 
  {
 
    $course = new Course($json['class']);
 

	
 
    if (!empty($json['sections']))
 
      $course->section_add(Section::from_json_arrays($json['sections']));
 
    
 
    if (!empty($json['dependencies']))
 
      foreach ($json['dependencies'] as $dependency)
 
	$course->dependency_add(Course::from_json_array($dependency));
 

	
 
    return $course;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Upgrade a course class to a newer version of that class.
 
   */
 
  public function __wakeup()
inc/class.schedule.php
Show inline comments
 
@@ -405,7 +405,7 @@ class Schedule
 
	if ($sort_time)
 
	  sort($time);
 

	
 
        echo '<div id="regDialog" title="Registration Codes"><p>Enter these codes into your school\'s online course registration system to register for classes:</p><div id="regDialogList"></div></div>';
 
        echo '<div id="regDialog" title="Registration Codes"></div>';
 
	echo '<div id="tabs">' . "\n" .
 
               '<div id="show-box" class="show-buttons">
 
                  <form action="#"><p class="nospace">
 
@@ -442,7 +442,14 @@ class Schedule
 
		
 
	for($i = $first_permutation; $i < $last_permutation; $i++)
 
	  {
 
             $syns = array();
 
	    /*
 
	     * 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();
 

	
 
	     echo  '      <div class="section" id="tabs-' . ($i+1) . "\">\n";
 
  
 
	    // Beginning of table
 
@@ -518,7 +525,15 @@ class Schedule
 
					. '<span class="location block">' . htmlentities($current_meeting->getLocation(), ENT_QUOTES) . "</span>\n"
 
					. '<span class="synonym block">' . htmlentities($section->getSynonym(), ENT_QUOTES) . "</span>\n"
 
					. "</td>\n";
 
				      $syns[$section->getSynonym()] = $section->getSynonym();
 

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

	
 
				      $filled = TRUE;
 
				    }
 
			}
 
@@ -542,11 +557,11 @@ class Schedule
 
		echo "          </tr>\n";
 
	      }
 

	
 

	
 

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

	
inc/class.section.php
Show inline comments
 
@@ -225,6 +225,42 @@ class Section
 
    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;
 
    $prof = NULL;
 
    foreach ($json_arrays as $meeting)
 
      {
 
	$letter = $meeting['section'];
 
	$synonym = $meeting['synonym'];
 
	$prof = $meeting['prof'];
 
	$section_meetings[] = SectionMeeting::from_json_array($meeting);
 
      }
 
    $section = new Section($letter, $section_meetings, $synonym, $prof);
 

	
 
    return $section;
 
  }
 

	
 
  /* for legacy unserialization */
 
  private $start;
inc/class.section_meeting.inc
Show inline comments
 
@@ -77,7 +77,7 @@ class SectionMeeting
 
   *
 
   * \param $days_str
 
   *   The days of the week in a string format. One char per
 
   *   day. Mon-Fri is represented with 'm', 't', 'w', 'h', 'f'.
 
   *   day. Mon-Sat is represented with 'm', 't', 'w', 'h', 'f', 's'.
 
   */
 
  private function days_set($days_str)
 
  {
 
@@ -226,4 +226,22 @@ class SectionMeeting
 

	
 
    return $json_array;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Parse a JSON array into a SectionMeeting.
 
   *
 
   * \param $json_array
 
   *   The JSON array to parse.
 
   * \return
 
   *   A shiny, new SectionMeeting.
 
   */
 
  public static function from_json_array(array $json_array)
 
  {
 
    $days = '';
 
    foreach ($json_array['days'] as $day => $meets)
 
      if ($meets)
 
	$days .= $day;
 
    return new SectionMeeting($days, $json_array['time_start'], $json_array['time_end'], $json_array['location'], $json_array['type']);
 
  }
 
}
inc/school.inc
Show inline comments
 
@@ -404,6 +404,48 @@ function school_example_course_id(array 
 

	
 
/**
 
 * \brief
 
 *   Populate a ``Registration Codes'' dialog.
 
 *
 
 * A school may override the default output by writing a function with
 
 * the same signature and semantics as this function with a name of
 
 * <school_id>_registration_html().
 
 *
 
 * \param $page
 
 *   The page object; used to conditionally format code as HTML or
 
 *   XHTML. Remember, you are writing an XHTML fragment and should not
 
 *   call Page::foot() or Page::head().
 
 * \param $school
 
 *   The school handle.
 
 * \param $courses
 
 *   An array of courses, where each course only has one section which
 
 *   is the section which the user chose.
 
 * \return
 
 *   A string which is a valid XHTML fragment. This fragment should
 
 *   direct the user to his school's registration services. It should
 
 *   also render the list of sections in a way appropriate to that
 
 *   school -- such as a list of fully-qualified section_ids or a
 
 *   listing of section synonyms.
 
 */
 
function school_registration_html(Page $page, array $school, array $courses)
 
{
 
  /*
 
   * The school from which to call the <school_id>_registration_html()
 
   * function. Used to fall back onto the 'default' school if the
 
   * selected school doesn't have a <school_id>_registration_html().
 
   */
 
  $function_school = $school;
 
  if (!function_exists($function_school['id'] . '_registration_html'))
 
    {
 
      $function_school = school_load('default');
 
      if (!function_exists($function_school['id'] . '_registration_Html'))
 
	return '<div class="error">Unable to load generic <tt>school_registration_html()</tt> function.</div>';
 
    }
 
  $school_registration_html = $function_school['id'] . '_registration_html';
 
  return $school_registration_html($page, $school, $courses);
 
}
 

	
 
/**
 
 * \brief
 
 *   Determine if a school has crawler data stored.
 
 *
 
 * \param $school
school.d/default.inc
Show inline comments
 
@@ -18,6 +18,9 @@
 
 * along with slate_permutate.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
$inc_dir = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'inc' . DIRECTORY_SEPARATOR;
 
require_once $inc_dir . 'class.page.php';
 

	
 
function default_info()
 
{
 
  return array('name' => 'Generic College',
 
@@ -51,3 +54,25 @@ function default_instructions_html()
 
</ol>
 
EOF;
 
}
 

	
 
/**
 
 * \brief
 
 *  A generic fallback for school_registration_html().
 
 *
 
 * \see school_registration_html()
 
 */
 
function default_registration_html(Page $page, array $school, array $courses)
 
{
 
  $html = ''
 
    . '  <p>' . PHP_EOL
 
    . '    Enter these codes into your school\'s online course registration' . PHP_EOL
 
    . '    system (<a href="' . htmlentities($school['url']) . '">' . htmlentities($school['name']) . '\'s website</a>)' . PHP_EOL
 
    . '    to register for classes:' . PHP_EOL
 
    . '  </p>' . PHP_EOL
 
    . '  <ul class="synonym-list">' . PHP_EOL;
 
  foreach ($courses as $course)
 
    foreach ($course as $section)
 
      $html .= '    <li>' . htmlentities($section->getSynonym()) . '</li>' . PHP_EOL;
 
  $html .= '  </ul>' . PHP_EOL;
 
  return $html;
 
}
scripts/displayTables.js
Show inline comments
 
@@ -41,6 +41,65 @@ function show_box_change()
 
    return false;
 
}
 

	
 
/**
 
 * \brief
 
 *   Do an AJAX loading of data with arbitrary error handling.
 
 *
 
 * \param target
 
 *   The jQuery object which should be populated with an error message
 
 *   or the result of loading.
 
 * \param data
 
 *   The data to send as a request.
 
 * \param handler
 
 *   A function with the signature handler(target, data) which is called upon
 
 *   a successful response. There is a default handler which uses
 
 *   .html() to load the data.data.html into target.
 
 * \param error_handler
 
 *   A function with the signature handler(target, status_text, data)
 
 *   which is called upon an error. The default error_handler will
 
 *   store an error message in target, possibly provided by
 
 *   data.message if the HTTP request itself was successful but the
 
 *   server still claimed there is an error. The third argument, data,
 
 *   will be null if the error is at the HTTP level.
 
 */
 
function slate_permutate_load(target, data, handler, error_handler)
 
{
 
    if (jQuery.type(handler) == 'undefined')
 
	handler = function(target, data)
 
	    {
 
		target.html(data.html);
 
	    }
 

	
 
    if (jQuery.type(error_handler) == 'undefined')
 
	error_handler = function(target, status_text, data)
 
	    {
 
		if (data)
 
		    if (data.message)
 
			target.html(data.message);
 
		    else
 
			target.html('<div class="error">Unknown error.</div>');
 
		else
 
		    target.html('<div class="error">HTTP error: ' + status_text + '</div>');
 
	    }
 

	
 
    jQuery.ajax({
 
                url: 'ajax.php',
 
		data: data,
 
		success: function(data, status_text, xhr)
 
		{
 
		    if (data && data.success && jQuery.type(data.data) != 'undefined')
 
			handler(target, data.data);
 
		    else
 
			error_handler(target, status_text, data);
 
		},
 
		dataType: 'json',
 
		error: function(xhr_jq, status_text, error)
 
		{
 
		    error_handler(target, status_text, null);
 
		}
 
		});
 
}
 

	
 
jQuery(document).ready( function()
 
  {
 
      jQuery('#show-box input').change(show_box_change);
 
@@ -48,21 +107,21 @@ jQuery(document).ready( function()
 

	
 
      jQuery("#regDialog").dialog({ modal: true, width: 550, resizable: false, draggable: false, autoOpen: false });   
 
      jQuery('#regCodes').click( function() {
 
        jQuery('#regDialogList').empty();
 
        jQuery('#regDialog').html('<p>Loading registration information...</p>');
 

	
 
	/* hmm... why isn't this information just stored in a global JS variable? */
 
	var tab_i = jQuery('#tabs').tabs('option','selected');
 
	var tab_fragment_i = /-([^-]+)$/.exec(jQuery('#the-tabs li:eq(' + tab_i + ') a').attr('href'))[1];
 
        var currSec = '.syns' + tab_fragment_i;
 
        var tab_course_data_json_selector = '.course-data-' + tab_fragment_i;
 
	
 
        var jHtml = jQuery(currSec).html();
 
        var secs = eval('(' + jHtml + ')');
 
        var output = '<p class=\'synList\'>';
 
        for( var i in secs ) {
 
          output = output + secs[i] + '<br />';
 
        }
 
        output = output + '</p>';
 
        jQuery('#regDialogList').append(output);
 
        var tab_course_data_json = jQuery(tab_course_data_json_selector).text();
 
        var tab_course_data = eval('(' + tab_course_data_json + ')');
 

	
 
        jQuery("#regDialog").dialog("open");
 
	slate_permutate_load(jQuery('#regDialog'), {school_registration_html: true, courses: tab_course_data});
 

	
 
        jQuery("#regDialog").dialog('open');
 

	
 
	return false;
 
      });
 
  }
 
);
styles/general.css
Show inline comments
 
@@ -256,13 +256,13 @@ a:hover {
 
  padding:0;
 
  margin:0;
 
}
 
.syns {
 
.course-data {
 
  display:none;
 
}
 
#regCodes {
 
  float: right;
 
}
 
.synList {
 
.synonym-list {
 
  margin-left: 1em;
 
}
 

	
0 comments (0 inline, 0 general)