© blackboard, inc. all rights reserved. clickers on campus: delivering a time-saving integration...
TRANSCRIPT
© Blackboard, Inc. All rights reserved.
Clickers on Campus: Delivering a Time-saving Integration
Kevin BorgesonDeveloperUniversity of Nebraska at Lincoln
2
Clicker System
» I will be talking specifically about the Interwrite PRS™ system from GTCO Calcomp
» Provides immediate feedback to instructors» Enhances class interactivity, engages students» 3 Main components to system
» Classroom receiver» Wireless IR transmitter w/unique ID (this is the
“clicker”)» Java based client software
3
Clicker System
» Clickers, receivers, and client software in the classroom
Roster
Lessons
Exports
Settings
Gradebook
ResponseMap
Session
Responses captured and transferred to classroom computer via serial port
All data stored as non-encrypted comma-delimited text files
Client software manages sessions and data
6
Clicker System
» Creating a New PRS Class - Roster
Roster information links a student to their clicker so that responses can be gathered. This information must be manually entered and maintained if there is no way to get updated roster files for importing.
This is the full path to this courses roster file. Copying
over this file with a correctly formatted .csv file will allow you to update the
roster information.
7
Clicker System Issues
» Client software lacks roster auto-updating feature
» Open nature of text files stored locally may expose sensitive data (e.g. student IDs, gradebook information, etc)
» Need a simple way for students to register their clickers, and for instructors to retrieve PRS formatted roster files for their classes
8
Solution
» Utilize Blackboard as a place for students to register their clickers» Already familiar with the Blackboard environment» Use students context information to simplify registration
process» Hook into the Blackboard course space to provide a
logical place for instructors to get their roster files, and to request automatic roster updates
» Store information in a MySQL database for easier retrieval
» Use Active Directory authentication to map a drive to a server folder for each instructor, then set that as the default location for the PRS client software» Keeps their data more confidential and secure» Allows us to overwrite the roster file on the server, thus
updating registered clicker information
9
MySQL Database Set-Up
» Clickers Table» Fields:id – int autoincrementprsid – varchar()studentid – varchar()
» PRS Courses Table» Fields:id – int autoincrementcourseid – varchar()
10
My.UNL Clicker Registration
» Blackboard registration page» Added link to existing Academic Resources tab
Tool Tabs assigned to the Student role
Link to the clicker registration page<a href="clicker_reg.jsp" class="sidelnks style1">register my clicker</a>
11
My.UNL Clicker Registration
If user already has a registered clicker, clicker ID shows here
User is required to enter clicker ID twice to verify entered ID is correct
12
My.UNL Clicker Registration
» Check For Registered ID…// get Users ID from contextUser user = bbContext.getUser();String batchUid = user.getBatchUid();
// if user already has clicker code in db, set flagString query = "SELECT prsid FROM prsids WHERE
studentid=?";PreparedStatement pstmt =
conn.prepareStatement(query);pstmt.setString(1, batchUid);ResultSet results = pstmt.executeQuery();
if (results.next()) { // record exists clickerCode = results.getString("prsid"); userFound = "true";}…<input type="text" name="clicker_code_1"
value='<%=clickerCode%>' size="9" maxlength="9"/>
…
» Verification Javascriptfunction verifyClickerCode() { if (document.regForm.clicker_code_1.value ==
document.regForm.clicker_code_2.value) { // if both codes match, go ahead and submit the
form document.regForm.submit(); } else { /*switchDiv sets the visibility of the DIV element
containing the error message for mis-matching clicker codes to visible, thus letting the user know they did not enter the same code twice*/
switchDiv("nomatch"); // clear the form values for the next attempt document.regForm.clicker_code_1.value = ""; document.regForm.clicker_code_2.value = ""; }}
13
My.UNL Clicker Registration» Receipt Page
This String set by variable strHeader
This String set by variable strMessage
14
…
String clickerCode = request.getParameter("clicker_code_1");if ((clickerCode.length() > 0) && (clickerCode.length() < 10)) { // make sure length is less than 10 digits for (int i=0;i<clickerCode.length();i++) { String sub = clickerCode.substring(i,i+1); if (sub.matches("\\D+")) { // check to make sure provided code only contains numbers strHeader = "Problem registering clicker code:"; strMessage = "The code you provided included incorrect characters. A correct clicker code should only contain numbers " + "with no letters or special characters. Please go back and try again."; isIllegalCharacter = true; } }
…
User user = bbContext.getUser();String userFound = request.getParameter("user_found");if (userFound.equals("false")) { strHeader = "Thank You, " + user.getGivenName() + "."; strMessage = "Your clicker code has been registered as " + clickerCode;
// do SQL update if user already has a record, insert if no code on file if (userFound.equals("true")) { String query = "UPDATE prsids SET prsid=? WHERE studentid=?"; PreparedStatement pstmt = conn.prepareStatement(query); pstmt.setString(1, clickerCode); pstmt.setString(2, user.getBatchUid()); pstmt.execute();
} else if (userFound.equals("false")) { String query = "INSERT INTO prsids (prsid,studentid) VALUES (?,?)"; PreparedStatement pstmt = conn.prepareStatement(query); pstmt.setString(1, clickerCode); pstmt.setString(2, user.getBatchUid()); pstmt.execute(); pstmt.close(); }
…
Validate users input
user_found passed as a hidden variable in POSTed form, lets us know if a user was found already (don’t have to make another database call)
Update users clicker ID record if they already have one registered
Insert new record in clickers database if none was found
15
Bookstore Clicker Registration
» Custom built Student ID proxy reader connected to a PRS receiver
» Placed in Bookstore, students can register their clickers immediately after purchase
» Runs on Linux distribution» Once a student presses a
button on their clicker, puts their student ID against the proxy reader, and confirms their information is correct, a record is added to the clickers database
16
PRS Tools Building Block
» Course Tool for Instructors» Instructors can request PRS roster files to
automatically be generated and updated in the PRS client software for this course
» View status of students clicker registrations» Export PRS formatted roster file
17
PRS Tools Building Block» Configuration Page
Data stored in “prs.prop” in Building Block’s Config directory:
…FileOutputStream fileConfiguration = null;…File fileConfigDir = PlugInUtil.getConfigDirectory("unl", "prstools");Properties pConfiguration = new Properties();// Set Properties here…
fileConfiguration = new FileOutputStream((fileConfigDir.getPath() + File.separator + "prs.prop"));
pConfiguration.store(fileConfiguration, "PRS Tools Configuration");
…
18
PRS Tools Building Block» BB-Manifest – Tool Definition…<application type="course" handle="prstools" use-ssl="false"
name="PRS Course Tools" can-allow-guest="false" small-icon="" large-icon=""> <description lang="en_US">Course tools for PRS
system.</description> <links> <link> <type value="course_tool"/> <name value="PRS Tools"/> <url value="prs/tools_index.jsp" /> <description value="Tools for interacting with the PRS Interwrite
system." /> </link> </links></application>…
19
PRS Tools Building Block
…<bbData:context id="ctx" entitlement="course.VIEW"><%Course crse = (Course) ctx.getCourse();Course.ServiceLevel csl = crse.getServiceLevelType();if (csl.equals(Course.ServiceLevel.COMMUNITY)) { %><!-- Show error page if this tool is accessed from a Bb Organization --><bbUI:receipt type="FAIL" title="Course Tool Only"><p>Access to this tool is only available within a course. You are currently in an
organization.</p></bbUI:receipt><% } else { %>…
20
PRS Tools Building Block» BB-Manifest – Permissions
…
<permissions> <permission type="attribute" name="user.authinfo"
actions="get" /> <permission type="attribute" name="user.personalinfo"
actions="get" /> <permission type="runtime" name="db.connection.bb_bb60" /> <permission type="socket" name="*" actions="connect,resolve" /></permissions>
…
21
PRS Tools Building Block
» Main Page - Components
<bbData:context id="ctx“ entitlement="course.VIEW">… Course crse = (Course) ctx.getCourse();…<bbUI:docTemplate><bbUI:coursePage courseId='<%=crse.getId()%>'>
<bbUI:breadcrumbBar environment="CTRL_PANEL" handle="control_panel"> <bbUI:breadcrumb>PRS Tools</bbUI:breadcrumb></bbUI:breadcrumbBar>…
Status bar and message customized based on if there is a record for this course in the Courses database
tools/export_registered.jsp?course_id="+crse.getId().toExternalString()
tools/view_registered.jsp?course_id="+crse.getId().toExternalString()
22
PRS Tools Building Block
» Changing PRS Roster Auto-Update Status» CourseID is persisted to
Courses database» Message and status bar
text changed» Name of roster file is
displayed for instructor (file name is CourseID+.csv)
23
PRS Tools Building Block
» Exporting Building Block
…// force client to recognize this as a file downloadresponse.setContentType("application/octet-
stream");response.setHeader("Content-
Disposition","attachment;filename="+course.getCourseId()+".csv");
…
24
PRS Tools Building Block
» PRS Roster File Format (BBDEVCONF.csv)» Clicker ID must be unique, otherwise client software will
make you choose one of the records and delete all others when you try to import
» Student ID field must be filled or you cannot grade your session, I just put the Blackboard username there (I would not put a students real ID there in case this text file got into the wrong hands)
» Net ID needs to be their Blackboard username, needed when trying to import grades into Blackboard
// Roster (Generated From Bb), Date&Time: 6/20/2006 1:56 PM, Class Name: Bb Developers ConferenceTransId, Group Id, Last Name, First Name, Middle Initial, Student ID, Nickname, Net ID416669,0,Test,PRS1,-,s-prs1,-,s-prs1416664,0,Test,PRS2,-,s-prs2,-,s-prs2416665,0,Test,PRS3,-,s-prs3,-,s-prs3416663,0,Test,PRS4,-,s-prs4,-,s-prs4416668,0,Test,PRS5,-,s-prs5,-,s-prs5
25
PRS Tools Building Block…
out.println("// Roster (Generated From Bb), Date&Time: " + dateTime + ", Class Name: " + course.getTitle(););out.println("TransId, Group Id, Last Name, First Name, Middle Initial, Student ID, Nickname, Net ID");Connection blackboardDefaultConnection = null;BbList memberships = null;
try {
blackboardDefaultConnection = ConnectionManager.getDefaultConnection();
// load all CourseMembership objects with STUDENT role// NOTE: must use method loadByCourseIdAndRole(Id,CourseMembership.Role,java.sql.Connection,boolean) to// specify you want the User information attached, otherwise null values will be returned with getUser()memberships = cmLoader.loadByCourseIdAndRole(courseId,CourseMembership.Role.STUDENT,blackboardDefaultConnection,true);
CourseMembershipComparator cmc = new CourseMembershipComparator();Collections.sort(memberships, cmc);
} catch (Exception e) {throw e;
} finally {ConnectionManager.releaseDefaultConnection(blackboardDefaultConnection);
}
…
Pull a Blackboard database connection from connection pool (needed to make heavyweight method call)
Sort students by Last name
Always release your connection back to the pool!
26
PRS Tools Building Block…String query = "SELECT prsid,studentid FROM prsids WHERE studentid=?";PreparedStatement pstmt = conn.prepareStatement(query);ResultSet results = null;
if (memberships.size() > 0) { // make sure there are students in the courseIterator iter = memberships.iterator();while (iter.hasNext()) { CourseMembership cm = (CourseMembership) iter.next(); User user = cm.getUser();
pstmt.setString(1,user.getBatchUid()); results = pstmt.executeQuery(); if (results.first()) { // found transmitter ID for student, output to file
transId = results.getString("prsid"); lastName = user.getFamilyName(); firstName = user.getGivenName(); if (user.getMiddleName().length() > 1) { middleInitial = user.getMiddleName().substring(0,1).toUpperCase(); } if (user.getMiddleName().length() == 1) { middleInitial = user.getMiddleName().toUpperCase(); }
studentId = user.getUserName();
netId = user.getUserName();
// print record to roster file out.println(transId+","+groupId+","+lastName+","+firstName+","+middleInitial+","+studentId+","+nickname+","+netId);
// reset middle initial for next record middleInitial="-"; }}…
Create a PreparedStatement object connected to the clickers database
As we iterate through each student enrolled in this course, bind their BatchUid to the already created PreparedStatement and execute a search for this user in the clickers database
27
PRS Tools Building Block» Viewing Clicker Registrations
…<bbUI:docTemplate><bbUI:coursePage courseId='<%=course.getId()%>'>
<bbUI:breadcrumbBar environment="CTRL_PANEL" handle="unl-prstools-nav-1"> <bbUI:breadcrumb>Clicker Registrations</bbUI:breadcrumb></bbUI:breadcrumbBar>…
…<bbUI:actionBar> <bbUI:actionItem title="<b>Registered</b>" href='<%=viewRegisteredLoc%>' imgUrl="/images/ci/icons/check.gif"/> <bbUI:actionItem title="Not Registered" href='<%=viewNotRegisteredLoc%>' imgUrl="/images/ci/icons/x.gif"/></bbUI:actionBar>…
…<bbUI:list collection="<%=registered%>" collectionLabel="Registered Users" objectId="m" className="UsersClickerTO" resultsPerPage="20"> <bbUI:listElement label="Clicker Id" name="clicker"> <%=m.getClickerId()%> </bbUI:listElement> <bbUI:listElement label="User Name" name="username"> <%=m.getUser().getUserName()%> </bbUI:listElement> <bbUI:listElement label="First Name" name="firstname"> <%=m.getUser().getGivenName()%> </bbUI:listElement> <bbUI:listElement label="Last Name" name="lastname"> <%=m.getUser().getFamilyName()%> </bbUI:listElement></bbUI:list>…
28
Generating Rosters For PRS Flagged Courses
» Standalone Java process generates PRS Roster files for all records found in the courses database (instructors who want their rosters automatically updated in the client software)
» Roster files are put in a folder with the instructors user login as the name
» Shell script executes the Java process, scheduled to run by a cron job
29
Generating Rosters For PRS Flagged Courses
» Shell Script
#!/bin/sh
JAVA_HOME=/usr/j2sdk1.4.2_02JAVA_EXEC=$JAVA_HOME/bin/javaBBDIR=/usr/local/blackboardBBLIB=$BBDIR/systemlibPRS_LIB=$BBDIR/data/prs/libPRS_CLASSES=$BBDIR/data/prs/classesCONFIG_FILE=$BBDIR/data/prs/prop/prs.propertiesVI=<put your Virtual Installation name here>FTP_BASE_DIR=/usr/local/blackboard/data/prs/ftpCP=.:$PRS_CLASSES:$PRS_LIB/mysql-connector-java-3.1.7-bin.jar:$PRS_LIB/jargp.jar:
$BBLIB/bb-platform.jar:$BBLIB/xercesImpl.jar:$BBLIB/xml-apis.jar:$BBLIB/jdbc/original.classes12.zip:$BBLIB/gnu-regexp-1.0.8.jar
cd ./prs
"$JAVA_EXEC" -cp "$CP" PrsRoster -V "$VI" -S "$CONFIG_FILE" -D "$FTP_BASE_DIR"
exit 0
This file initializes services managed by class blackboard.platform.BbServiceManager (which we need to get access to loaders and persisters). I just copied the file service-config-snapshot-jdbc.properties and changed the parameter ‘blackboard.service.log.param.logdef.default.logFile’ to point to a new log file.
30
Generating Rosters For PRS Flagged Courses…try { BbServiceManager.init( config ); } catch (Exception e) { System.out.println("Error initializing BbServiceManager."); throw e; } VirtualInstallationManager vMgr = null; VirtualHost vHost = null;
try { cMgr = (ContextManagerServerImpl)BbServiceManager.lookupService( ContextManager.class ); vMgr = (VirtualInstallationManager)BbServiceManager.lookupService( VirtualInstallationManager.class ); vHost = vMgr.getVirtualHost( vi ); cMgr.setContext(vHost); } catch (Exception e) { System.out.println("Error finding virtual host name"); throw e; } try { bbPm = BbServiceManager.getPersistenceService().getDbPersistenceManager(); crseLoader = (CourseDbLoader) bbPm.getLoader( CourseDbLoader.TYPE ); cmLoader = (CourseMembershipDbLoader) bbPm.getLoader( CourseMembershipDbLoader.TYPE ); } catch (Exception e) { System.out.println("Error initializing DbPersistenceManager"); throw e; }…
This is the config file passed in the command line /usr/local/blackboard/data/prs/prop/prs.properties
Once we get the DbPersistenceManager loaded, we can use Blackboard’s loaders and persisters
31
Generating Rosters For PRS Flagged Courses…Course crse = null;String query = "SELECT courseid FROM prscourse";Statement stmt = prsCoursesConn.createStatement();ResultSet results = stmt.executeQuery(query);
// loop through each course found and generate a PRS Rosterwhile (results.next()) { try { crse = (Course) crseLoader.loadByCourseId(results.getString("courseid")); } catch (KeyNotFoundException e) { // if this course is not found loop to the next course continue; }
BbList instructorMemberships = cmLoader.loadByCourseIdAndRole(crse.getId(),CourseMembership.Role.INSTRUCTOR,blackboardDefaultConn,true);
BbList studentMemberships = cmLoader.loadByCourseIdAndRole(crse.getId(),CourseMembership.Role.STUDENT,blackboardDefaultConn,true);
String roster = generatePrsCourseRoster(studentMemberships, results.getString("courseid"));
Iterator iter = instructorMemberships.iterator(); while(iter.hasNext()) { CourseMembership cm = (CourseMembership) iter.next(); String instructorName = cm.getUser().getUserName(); String rosterName = results.getString("courseid") + ".csv"; (new File(pr.m_fdFlag+"/"+instructorName+"/PRS/Roster")).mkdirs(); try { BufferedWriter out = new BufferedWriter(new FileWriter(pr.m_fdFlag+"/"+instructorName+"/PRS/Roster/"+rosterName)); out.write(roster); out.close(); } catch (IOException e) { System.out.println("Error writing roster file"); throw e; }}…
This subroutine will return a PRS formatted file stored in a String object
Write a roster file to each instructors folder
32
Transfer Updated PRS Roster Files
» Transfer generated folder structure to a Windows Server» Require Active Directory logins on classroom computers» Map a drive based on their Active Directory login name to their folder on the
server» Set the default user profile on the classroom computer PRS client folder to
this mapped drive» Roster files updated on the server will now be reflected in the classroom via
mapped drive» Only have permissions to see what is in their folder, data is partitioned, cant
see others data
Login
PRS
Roster
Class1.csv
Class2.csv
33
Overview Diagram
PRS Courses
Clickers
Active Directory
PRS Roster File
Generator
Course Tool
Clicker Registrat
ion
Bookstore Clicker Registrat
ion
Login
PRS
Roster
Class1.csv
Class2.csv
Login
PRS
Roster
Class1.csv
Class2.csv
Blackboard Server
Classroom Computer – PRS client software default folder set to mapped drive
Windows Server
PRS Roster files updated via FTP
Export PRS Roster manually – save file to local computer
Log in to Active Directory account – drive is mapped to your login on Windows server
Mapped drive gives instructor access only to folder with their login name
34
Conclusion
» 26 PRS Flagged Courses» 22 PRS equipped classrooms on campus» Students own their clicker for the duration
of their academic career (assuming they don’t lose or destroy it)
» Currently $30 for a clicker