The Idea
Prof +Tom Stoneham and Nick Jones had the idea of using Google Sites as an alternative to textual documents for student work, in this case, a dissertation about a certain philosopher.
Google Sites give the opportunity for the creation of a network of information rather than a narrative document. A site can hold videos, audio and refer to other online resources with links.
The idea was that there would be a simple template site (see above), with boiler plate text and guidance about copyright issues etc. and the student could then start editing existing pages and creating new ones.
Administration
From an administrational perspective, the Google sites would need to be closed to student when the deadline was met. Ideally, it would good if the student could have a copy of their Google Site - both to continue working on it and to use in their portfolio of work.
Whilst Tom didn't need the student's identity to be anonymized, but we used a unique reference number for the name of the site anyway.
Technical
From a technical perspective, creating a 80 Google Sites would seem to be a simple task. It would be, were it not for the fact that often, when creating resources like Google Sites or Calendars or Groups there can be a variable lag from asking Google to create the resource and it being available for further use. And whilst there is a lag, the Apps Script code you write to create a Google site is asynchronous, which effectively means you do the coding equivalent of ...
var basket = new Basket()
basket.addEggs( 12 ) // At this point there probably is NO BASKET! Error!
The "solution" I hit upon was using a once-a-minute Trigger to pick off the tasks I wanted to run one at time, meaning that after each one Google's system have more than enough time to "catch up". The tasks being...
- create a Google Site from a template
- add the Student as Editor
- When the deadline is reached, add the examiner to the site as Viewer and remove the student as Editor
- Email the examiner that they have a site to mark
- create a copy of the student's Google Site and make them Owner for their portfolio.
I created a spreadsheet with three sheets ( to the wind ). The first, Administration is where you set up the name of your project, which Google Site to use as your template and when the deadline is. The second, Students is a list of student emails, unique references and examiner emails. And the last is a utility sheet that gets a list of your Google Sites ( for interface niceness ).
I then created a huge function to gently create the Sites and add the right permissions to the right people at the right time. It looks like this.
function triggered_site_maker( ){
var ss = SpreadsheetApp.getActiveSpreadsheet( );
var sheet = ss.getSheetByName( "Students" );
// Get default values from the Administration sheet
var admin_sheet = ss.getSheetByName("Administration");
var template_site_name = admin_sheet.getRange("B1").getValues()
var domain = admin_sheet.getRange("B4").getValue( )
var homework_id = admin_sheet.getRange("B5").getValues( )
var homework_title = admin_sheet.getRange("B6").getValues( )
// get all the data in the Students sheet
var range = sheet.getDataRange().getValues();
var students = rangeToObjects(range);
for(var i = 0; i < students.length; i++){
var student = students[i];
var student_unique_ref = student.uniquereference.toString().toLowerCase()
var new_site_name = homework_id + "-" + student_unique_ref
var student_email = student.email
var examiner = student.examiner
var siteurl = student.siteurl
var status = student.status
var statuscellname = "E" + (i +2 )
var timestampcellname = "F" + ( i +2 )
var timestamp =new Date()
var cellname = "D" + ( i +2 ) //
if ( student_unique_ref != ''){
switch (status){
case '': // We're at the beginning, make a site for the student.
try{
// Note: sites_url is a NEW sites url not an already made one.
var sites_url = create_a_site(template_site_name, new_site_name )
sheet.getRange(cellname).setValue(sites_url)
sheet.getRange(statuscellname).setValue('site made')
sheet.getRange( timestampcellname ).setValue( timestamp )
break
}catch(e){
ErrorMail(e)
}
case 'site made': // The site has been made, now add the student.
try{
var name = SiteUrlToName(siteurl) // workout site's name from URL. Had problems with getSiteByUrl()
if (domain == ''){
var site = SitesApp.getSite( name)
}else{
var site = SitesApp.getSite(domain, name)
}
site.addEditor(student_email);
sheet.getRange(statuscellname).setValue('site made and student added')
site.setTitle( homework_title ) // Handy for searching later I guess. Gulp.
sheet.getRange( timestampcellname ).setValue( timestamp )
}catch (e) {
ErrorMail(e)
}
break
// NOTIFY EXAMINER/REMOVE STUDENT AND COPY SITE
case 'site made and student added':
var deadline = admin_sheet.getRange("B2").getValue( )
var now = new Date()
if (now > deadline){
try{
var name = SiteUrlToName(siteurl) // workout site's name from URL
if (domain == ''){
var site = SitesApp.getSite( name)
}else{
var site = SitesApp.getSite(domain, name)
}
site.removeEditor( student_email )
sheet.getRange(statuscellname).setValue('student removed')
sheet.getRange( timestampcellname ).setValue( timestamp )
}catch(e){
ErrorMail(e)
}
}
break
case 'student removed': //Now, add the examiner
try{
var name = SiteUrlToName(siteurl) // workout site's name from URL
if (domain == ''){
var site = SitesApp.getSite( name)
}else{
var site = SitesApp.getSite(domain, name)
}
var site_name = site.getName()
Logger.log( site_name)
var viewers = site.getViewers( )
if ( examiner != ''){
site.addViewer( examiner )
sheet.getRange(statuscellname).setValue('examiner added')
sheet.getRange( timestampcellname ).setValue( timestamp )
}
break
}catch(e){
ErrorMail(e)
}
case 'examiner added': //Email them with a link to the site
try{
MailApp.sendEmail(examiner,
"A Google Site To Mark",
"As an examiner, a Google Site called '" + homework_title +"' has been created for you to mark. \n\n " +
"Unique student reference: " + student_unique_ref + "\n\n " +
"url: " + siteurl + "\n\n",
{name:"Google Site Marking", noReply:true});
sheet.getRange(statuscellname).setValue('examiner emailed')
sheet.getRange( timestampcellname ).setValue( timestamp )
break
}catch(e){
ErrorMail(e)
}
case 'examiner emailed': //Make the student a copy for their portfolio.
try{
var name = SiteUrlToName(siteurl) // workout site'ss name from URL
if (domain == ''){
var site = SitesApp.getSite( name)
}else{
var site = SitesApp.getSite(domain, name)
}
var new_name = "" + name + "-copy"
var title = site.getTitle()
var summary = site.getSummary()
var site_name = site.getName( ) // use this site as a template
if (domain == ''){
var copied_site = SitesApp.copySite( new_name , title, summary, name)
}else{
var copied_site = SitesApp.copySite( domain, new_name , title, summary, name)
}
Utilities.sleep(4000)
var copied_site_url = copied_site.getUrl()
sheet.getRange( "I" + (i + 2) ).setValue( )
sheet.getRange(statuscellname).setValue('student copy created')
sheet.getRange( timestampcellname ).setValue( timestamp )
break
}catch(e){
ErrorMail(e)
}
case 'student copy created': // Add them as owners to these copies.
try{
var copied_url = sheet.getRange( "I" + (i + 2) ).getValue()
var name = SiteUrlToName(siteurl)
if (domain == ''){
var site = SitesApp.getSite( name)
}else{
var site = SitesApp.getSite(domain, name)
}
site.addOwner(student_email)
sheet.getRange(statuscellname).setValue('student added to copy')
sheet.getRange( timestampcellname ).setValue( timestamp )
break
}catch(e){
ErrorMail(e)
}
case 'student added to copy':
sheet.getRange(statuscellname).setValue('Completed')
sheet.getRange( timestampcellname ).setValue( timestamp )
break
}//end case
}//end if empty student_unique_ref --
}//end forloop
// It would be good at this point if we could then de-activate the trigger....
}
function test_trigger(){
triggered_site_maker()
}
function create_a_site( template_site_name, new_site_name ){
var ss = SpreadsheetApp.getActiveSpreadsheet( );
var admin_sheet = ss.getSheetByName("Administration");
var domain = admin_sheet.getRange("B4").getValues( )
if ( domain == ''){
var template_site = SitesApp.getSite( template_site_name )
}else{
var template_site = SitesApp.getSite(domain, template_site_name );
}
var title = template_site.getTitle()
var summary = template_site.getSummary()
// See the warning in https://developers.google.com/apps-script/class_sitesapp
if ( domain == ''){
var site = SitesApp.copySite( new_site_name, title, summary, template_site);
}else{
var site = SitesApp.copySite( domain, new_site_name, title, summary, template_site);
}
Utilities.sleep( 3000 ) // Yawn!
var sites_url = site.getUrl( );
return sites_url ;
}
function my_sites_names(){ ///// GET A LIST OF SITES YOU'VE MADE
// This is used in the interface as a data validation thing.
var ss = SpreadsheetApp.getActiveSpreadsheet( );
var admin_sheet = ss.getSheetByName("Administration");
var my_sites_sheet = ss.getSheetByName("My Sites");
// clear the ole data...
var last_row = my_sites_sheet.getDataRange().getLastRow()
var range = my_sites_sheet.getRange(2, 2, last_row)
range.clear()
var domain = admin_sheet.getRange("B4").getValue( )
if (domain == ''){
var sites = SitesApp.getSites()
}else{
var sites = SitesApp.getSites(domain)
}
var site_names = new Array();
for(var i = 0; i < sites.length; i++){
var site = sites[i]
var site_name = site.getName()
//set name
var range = my_sites_sheet.getRange(i+2, 1)
range.setValue( site_name )
Logger.log( site_name )
//set URL
var range = my_sites_sheet.getRange(i+2, 3)
range.setValue( site.getUrl() )
site_names.push( site.getName() )
}
return site_names
}
Take a Copy And Try It Yourself
Note: This will only work with Apps for Education/Business accounts ( and won't work for consumer accounts because you can't create a Google Site for random people willy nilly ).
1. File > Make a copy of the
spreadsheet here.
2. Go to the Script Editor and Run Event Handling > onOpen() . This authenticates the Script to ask which are your Google Sites. This is just for interface niceness.
3. Fill in the Administration sheet. This needs a domain and a deadline. There's also a "project ID" which can be anything, but is used to differentiate different "assignments".
4. Add students, unique references and examiners to the Students sheet ( there is some Utility code to help generate them if you don't have unique IDs ).
5. You might want to add your email address to the Utility > ErrorEmail function. When a Script is running from a Trigger I don't think you get access to any Logger.log() messages, so this is a handy way of debugging the app.
6. You are now ready to run the triggered_site_maker() function. You can run it straight from the ScriptEditor, but you will need to add it as a Trigger for it to do all it needs to do. Like this...
Once running, you should see various values being populated as your Google Sites get created and permissions added. When the deadline is met, the students are removed as Editors and given a copy of their site. Once this is finished you can delete the Trigger.