© blackboard, inc. all rights reserved. clickers on campus: delivering a time-saving integration...

35
© Blackboard, Inc. All rights reserved. Clickers on Campus: Delivering a Time- saving Integration Kevin Borgeson Developer University of Nebraska at Lincoln

Upload: lee-randolph-patterson

Post on 26-Dec-2015

213 views

Category:

Documents


0 download

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

4

Clicker Demo:Building Blocks Survey

5

Clicker System

» Creating a New PRS Class - Information

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

35

Q&A

Questions??

Contact Information: [email protected]