/*==========================================================================*\
| $Id: CoreNavigator.java,v 1.6 2012/05/09 14:25:07 stedwar2 Exp $
|*-------------------------------------------------------------------------*|
| Copyright (C) 2006-2012 Virginia Tech
|
| This file is part of Web-CAT.
|
| Web-CAT 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.
|
| Web-CAT 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 General Public License for more details.
|
| You should have received a copy of the GNU Affero General Public License
| along with Web-CAT; if not, see <http://www.gnu.org/licenses/>.
\*==========================================================================*/
package org.webcat.core;
import org.apache.log4j.Logger;
import org.webcat.core.CoreNavigatorObjects.CourseOfferingSet;
import org.webcat.ui.generators.JavascriptGenerator;
import org.webcat.ui.util.ComponentIDGenerator;
import com.webobjects.appserver.WOActionResults;
import com.webobjects.appserver.WOComponent;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WOResponse;
import com.webobjects.eocontrol.EOSortOrdering;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSMutableArray;
import er.extensions.eof.ERXQ;
import er.extensions.foundation.ERXArrayUtilities;
//--------------------------------------------------------------------------
/**
* The popup course selector that serves as the basis for the Web-CAT core
* subsystem navigation scheme.
*
* <h2>Bindings</h2>
* <dl>
* <dt>allowsAllSemesters</dt>
* <dd>A boolean value that adds an option to the semester drop-down that
* allows the user to select "All" semesters. If false, the user can only
* select a single semester. Defaults to true.</dd>
* <dt>allowsAllOfferingsForCourse</dt>
* <dd>A boolean value that adds an option for each course in the course
* drop-down that allows the user to select "All" offerings for that course. If
* false, the user may only select a single offering for a single course.
* Defaults to true.</dd>
* <dt>includeAdminAccess</dt>
* <dd>A boolean value indicating whether the course drop-down should include
* courses that the user is not teaching or enrolled in but does have admin
* access for. If the user is an administrator, he can change this in the user
* interface. Defaults to false.</dd>
* <dt>includeWhatImTeaching</dt>
* <dd>A boolean value indicating whether the course drop-down should include
* courses that the user is teaching. If the user has TA privileges or higher,
* he can change this in the user interface. Defaults to true.</dd>
* </dl>
*
* @author Tony Allevato
* @author latest changes by: $Author: stedwar2 $
* @version $Revision: 1.6 $ $Date: 2012/05/09 14:25:07 $
*/
public class CoreNavigator
extends WCComponent
{
//~ Constructors ..........................................................
// ----------------------------------------------------------
/**
* Create a new object.
*
* @param context
*/
public CoreNavigator(WOContext context)
{
super(context);
}
//~ KVC attributes (must be public) .......................................
public NSMutableArray<INavigatorObject> courseOfferings;
public INavigatorObject courseOfferingInRepetition;
public INavigatorObject selectedCourseOffering;
public NSMutableArray<INavigatorObject> semesters;
public INavigatorObject semesterInRepetition;
public INavigatorObject selectedSemester;
public Boolean allowsAllSemesters;
public Boolean allowsAllOfferingsForCourse;
public Boolean startOpen;
public ComponentIDGenerator idFor;
public static final String COURSE_OFFERING_SET_KEY = "courseOfferingSet";
//~ Methods ...............................................................
// ----------------------------------------------------------
@Override
public void appendToResponse(WOResponse response, WOContext context)
{
log.debug("entering appendToResponse()");
idFor = new ComponentIDGenerator(this);
updateSemesters();
super.appendToResponse(response, context);
log.debug("leaving appendToResponse()");
}
// ----------------------------------------------------------
public void awake()
{
log.debug("entering awake()");
if (selectionsParent == null)
{
selectionsParent = findNearestAncestor(WCCourseComponent.class);
if (selectionsParent == null)
{
throw new IllegalStateException("CoreNavigator can only be "
+ "embedded inside a WCCourseComponent page");
}
if (allowsAllSemesters == null)
{
allowsAllSemesters = Boolean.valueOf(
selectionsParent.allowsAllSemesters());
}
if (allowsAllOfferingsForCourse == null)
{
allowsAllOfferingsForCourse = Boolean.valueOf(
selectionsParent.allowsAllOfferingsForCourse());
}
if (startOpen == null)
{
startOpen = Boolean.valueOf(
selectionsParent.forceNavigatorSelection());
}
Semester semester = selectionsParent.coreSelections().semester();
if (semester != null)
{
selectedSemester =
new CoreNavigatorObjects.SingleSemester(semester);
}
// Handle course preference
Course course = selectionsParent.coreSelections().course();
if (course != null && allowsAllOfferingsForCourse)
{
wantOfferingsForCourse = course;
}
else
{
CourseOffering co =
selectionsParent.coreSelections().courseOffering();
if (co != null)
{
selectedCourseOffering =
new CoreNavigatorObjects.SingleCourseOffering(co);
wantOfferingsForCourse = null;
}
}
}
if (selectedRoleAccessLevel < 0)
{
TabDescriptor selectedRole =
((Session)session()).tabs.selectedChild();
if (selectedRole != null)
{
selectedRoleAccessLevel = selectedRole.accessLevel();
}
else
{
selectedRoleAccessLevel = 0;
}
}
log.debug("selected semester = " + selectedSemester);
log.debug("want offerings for = " + wantOfferingsForCourse);
log.debug("selected course = " + selectedCourseOffering);
log.debug("selectionsParent = "
+ selectionsParent.getClass().getName());
super.awake();
log.debug("leaving awake()");
}
// ----------------------------------------------------------
/**
* Updates the list of available semesters, followed by course offerings
* and assignments.
*
* @return the result is ignored
*/
public JavascriptGenerator updateSemesters()
{
log.debug("updateSemesters()");
semesters = new NSMutableArray<INavigatorObject>();
if (allowsAllSemesters)
{
semesters.addObject(new CoreNavigatorObjects.AllSemesters(
localContext()));
}
NSArray<Semester> sems = Semester.allObjectsOrderedByStartDate(
localContext());
for (Semester sem : sems)
{
semesters.addObject(new CoreNavigatorObjects.SingleSemester(sem));
}
if (selectedSemester == null)
{
selectedSemester = semesters.objectAtIndex(0);
}
if (log.isDebugEnabled())
{
log.debug("semesters = " + semesters);
log.debug("selected semester = " + selectedSemester);
}
return updateCourseOfferings();
}
// ----------------------------------------------------------
public boolean isCourseOfferingFilterPlaceholder()
{
return courseOfferingInRepetition ==
CoreNavigatorObjects.FILTER_PLACEHOLDER;
}
// ----------------------------------------------------------
protected void gatherCourseOfferings()
{
log.debug("gatherCourseOfferings()");
courseOfferings = new NSMutableArray<INavigatorObject>();
if (user().hasAdminPrivileges())
{
courseOfferings.addObject(CoreNavigatorObjects.FILTER_PLACEHOLDER);
}
@SuppressWarnings("unchecked")
NSArray<Semester> sems = (NSArray<Semester>)
selectedSemester.representedObjects();
TabDescriptor selectedRole = ((Session)session()).tabs.selectedChild();
boolean isStaffRole = false;
if (selectedRole != null)
{
isStaffRole = selectedRoleAccessLevel >= User.GTA_PRIVILEGES;
}
// First, get all the course offerings we're interested in based on
// the user's access level and selections in the UI. This may include
// more offerings that we really need.
if (user().hasAdminPrivileges() && includeAdminAccess())
{
unfilteredOfferings = CourseOffering.allObjects(localContext());
}
else if (!isStaffRole)
{
@SuppressWarnings("unchecked")
NSArray<CourseOffering>temp = ERXArrayUtilities
.arrayByAddingObjectsFromArrayWithoutDuplicates(
user().staffFor(), user().enrolledIn());
unfilteredOfferings = temp;
}
else
{
unfilteredOfferings = user().staffFor();
}
// Next, filter the course offerings to include only those that occur
// during the semester(s) that the user has selected.
NSArray<CourseOffering> offerings =
ERXQ.filtered(unfilteredOfferings, ERXQ.in("semester", sems));
// Now sort the course offerings by course department and number.
offerings = EOSortOrdering.sortedArrayUsingKeyOrderArray(offerings,
EntityUtils.sortOrderingsForEntityNamed(
CourseOffering.ENTITY_NAME));
// Add each course offering to the list, also adding an "All" option
// before a set of courses if the user has access to every offering of
// a course and if that option is selected.
Course lastCourse = null;
INavigatorObject oldSelection = selectedCourseOffering;
selectedCourseOffering = null;
for (int i = 0; i < offerings.count(); i++)
{
CourseOffering offering = offerings.objectAtIndex(i);
if (lastCourse == null || !lastCourse.equals(offering.course()))
{
if (allowsAllOfferingsForCourse)
{
int mismatchIndex = i + 1;
for (; mismatchIndex < offerings.count(); mismatchIndex++)
{
// Find first offering later in list from a
// different course
if (!offering.course().equals(
offerings.objectAtIndex(mismatchIndex).course()))
{
break;
}
}
// If there was more than one offering for
// the initial course
if (mismatchIndex > i + 1)
{
CourseOffering exemplar = null;
NSMutableArray<CourseOffering> subset =
new NSMutableArray<CourseOffering>(
mismatchIndex - i);
for (int k = i; k < mismatchIndex; k++)
{
exemplar = offerings.objectAtIndex(k);
subset.add(exemplar);
}
INavigatorObject courseWrapper =
new CoreNavigatorObjects
.CourseOfferingSet(subset);
courseOfferings.addObject(courseWrapper);
if ((oldSelection != null
&& oldSelection.equals(courseWrapper))
|| (wantOfferingsForCourse != null
&& exemplar != null
&& wantOfferingsForCourse == exemplar.course()))
{
selectedCourseOffering = courseWrapper;
}
}
}
lastCourse = offering.course();
}
INavigatorObject newOffering =
new CoreNavigatorObjects.SingleCourseOffering(offering);
courseOfferings.addObject(newOffering);
if ((oldSelection != null && oldSelection.equals(newOffering))
|| (selectedCourseOffering == null
&& wantOfferingsForCourse != null
&& wantOfferingsForCourse == offering.course()))
{
selectedCourseOffering = newOffering;
}
}
/*if (selectedCourseOffering == null && courseOfferings.count() > 0)
{
selectedCourseOffering = courseOfferings.objectAtIndex(0);
}*/
if (log.isDebugEnabled())
{
log.debug("courseOfferings = " + courseOfferings);
log.debug("selected course offering = " + selectedCourseOffering);
}
}
// ----------------------------------------------------------
/**
* Updates the list of course offerings.
*
* @return the result is ignored
*/
public JavascriptGenerator updateCourseOfferings()
{
log.debug("updateCourseOfferings()");
gatherCourseOfferings();
return new JavascriptGenerator()
.refresh(idFor.get("coursePane"))
.append("window.navUnderlay.hide(); window.navUnderlay.destroyRecursive()");
}
// ----------------------------------------------------------
public JavascriptGenerator updateCourseOfferingsAndClearSelection()
{
log.debug("updateCourseOfferingsAndClearSelection()");
saveSemesterSelection();
if (selectedCourseOffering != null)
{
NSArray<?> offerings = selectedCourseOffering.representedObjects();
if (offerings != null && offerings.count() > 0)
{
wantOfferingsForCourse =
((CourseOffering)offerings.objectAtIndex(0)).course();
}
}
selectedCourseOffering = null;
JavascriptGenerator result = updateCourseOfferings();
if (selectedCourseOffering != null)
{
saveCourseSelection();
result = new JavascriptGenerator().redirectTo("");
}
return result;
}
// ----------------------------------------------------------
public JavascriptGenerator updateCourseOfferingsMenu()
{
log.debug("updateCourseOfferingsMenu()");
gatherCourseOfferings();
return new JavascriptGenerator().refresh(idFor.get("courseMenu"));
}
// ----------------------------------------------------------
protected void saveSemesterSelection()
{
// Save semester choice
if (selectedSemester != null)
{
NSArray<?> sems = selectedSemester.representedObjects();
if (sems != null && sems.count() == 1)
{
Semester selected = (Semester)sems.objectAtIndex(0);
if (log.isDebugEnabled())
{
log.debug("saving semester selection = " + selected);
}
selectionsParent.coreSelections().setSemester(selected);
}
else if (allowsAllSemesters)
{
log.debug("saving semester selection = null (all semesters)");
selectionsParent.coreSelections().setSemester(null);
}
}
}
// ----------------------------------------------------------
protected void saveCourseSelection()
{
// Save course or course offering choice
if (selectedCourseOffering != null)
{
NSArray<?> offerings = selectedCourseOffering.representedObjects();
if (offerings != null && offerings.count() > 0)
{
CourseOffering co = (CourseOffering)offerings.objectAtIndex(0);
boolean allOfferings = allowsAllOfferingsForCourse &&
(offerings.count() > 1
|| selectedCourseOffering instanceof CourseOfferingSet);
if (allOfferings)
{
if (log.isDebugEnabled())
{
log.debug("saving course selection = " + co.course()
+ " (all)");
}
selectionsParent.coreSelections()
.setCourseRelationship(co.course());
CourseOffering co2 =
selectionsParent.coreSelections().courseOffering();
if (co2 != null && co2.course() != co.course())
{
log.debug("saving course offering selection = null");
selectionsParent.coreSelections()
.setCourseOfferingRelationship(null);
}
else if (log.isDebugEnabled())
{
log.debug("leaving course offering selection = " + co2);
}
}
else
{
if (log.isDebugEnabled())
{
log.debug("saving course selection = null");
log.debug("saving course offering selection = " + co);
}
selectionsParent.coreSelections()
.setCourseRelationship(null);
selectionsParent.coreSelections()
.setCourseOfferingRelationship(co);
}
}
}
}
// ----------------------------------------------------------
protected void saveSelections()
{
saveSemesterSelection();
saveCourseSelection();
}
// ----------------------------------------------------------
/**
* Invoked when the OK button in the dialog is pressed.
*
* @return null to reload the current page
*/
public WOActionResults okPressed()
{
log.debug("okPressed()");
selectionsParent.flushNavigatorDerivedData();
saveSelections();
return null;
}
// ----------------------------------------------------------
public void setIncludeWhatImTeaching(boolean includeWhatImTeaching)
{
selectionsParent.coreSelections().setIncludeWhatImTeaching(
includeWhatImTeaching);
}
// ----------------------------------------------------------
public boolean includeWhatImTeaching()
{
return selectionsParent.coreSelections().includeWhatImTeaching();
}
// ----------------------------------------------------------
public JavascriptGenerator toggleIncludeWhatImTeaching()
{
setIncludeWhatImTeaching(!includeWhatImTeaching());
return updateCourseOfferingsMenu();
}
// ----------------------------------------------------------
public void setIncludeAdminAccess(boolean includeAdminAccess)
{
selectionsParent.coreSelections().setIncludeAdminAccess(
includeAdminAccess);
}
// ----------------------------------------------------------
public boolean includeAdminAccess()
{
return selectionsParent.coreSelections().includeAdminAccess();
}
// ----------------------------------------------------------
public JavascriptGenerator toggleIncludeAdminAccess()
{
setIncludeAdminAccess(!includeAdminAccess());
return updateCourseOfferingsMenu();
}
// ----------------------------------------------------------
/**
* Searches up the WOComponent hierarchy searching for the nearest
* enclosing ancestor that is an instance of the specified target
* class.
* @param targetClass The type of ancestor to look for
* @return The identified ancestor, if one is found, or null if
* none matching the target class is found.
*/
@SuppressWarnings("unchecked")
protected <T> T findNearestAncestor(Class<T> targetClass)
{
WOComponent ancestor = parent();
while (ancestor != null
&& (!(targetClass.isInstance(ancestor))
|| PageWithCourseNavigation.class.isInstance(ancestor)))
{
ancestor = ancestor.parent();
}
return (T)ancestor;
}
//~ Static/instance variables .............................................
protected WCCourseComponent selectionsParent = null;
private Course wantOfferingsForCourse;
private int selectedRoleAccessLevel = -1;
private NSArray<CourseOffering> unfilteredOfferings = null;
static Logger log = Logger.getLogger(CoreNavigator.class);
}