Showing posts with label spreadsheet. Show all posts
Showing posts with label spreadsheet. Show all posts

Thursday, May 9, 2013

Information Freedom Fighting


My eye caught the City of York Council announcing that they publish all the "Freedom of Information" requests as PDFs ( here ).

The sharp-eyed amongst you will spot that the requests are organised by weeks. Each week's requests are stored in a PDF for that week. Each PDF would need clicking through to that week, then clicking through to that page and then downloading separately (using the handy "Download Now" link ) and then reading. The search engine is pretty hopeless and can't just return FOI requests and so gives you hundreds of results for any query.

Organising FOI requests by week is completely ridiculous, almost as ridiculous as ordering them by the number of words used or alphabetically. Now of course it probably makes sense from the point of view of compiling the requests - it sounds like a "once a week" job for somebody, but to then publish them once a week seems madness.

One of my pet hates is information that is made available but totally impossible to use. It's like saying, "Yes, of course you can have all the data we keep on you, we have written it on mist on these eggshells - would you like us to post it to you?". It's exactly like that.

So, one evening, I wondered if I could retrospectively do something more useful with their data. It is open, so why not.

First I made a crawler with ScraperWiki ( what an excellent tool this is ) that follows all those links and grabs the text from the PDFs. I based this on Martin's Scraper ( thanks Martin ).

I then downloaded the data collected as CSV and tried using the free statistical tool Sci2 for stemming and combining the words. Whilst this approach worked, I didn't like the resulting stemmed words, like glaz, because they just look so unfriendly.

Next, I wrote a python script to strip out stopwords like "the" and "where" etc and count the word popularity of each word in the downloaded file,  keeping track of the URL that it came from, and saved it back to a new .csv file.

from string import *
import re, HTMLParser
def get_urls(word):
f = '/Users/tomsmith/Downloads/pdfextractor_1.csv'
lines = open(f).readlines()
urls =[]
for line in lines:
url = line.split(",")[0].strip()
text = lower(line.split(",")[1].strip())
words = text.split(" ")
if word in words:
urls.append( url )
return urls

class MLStripper(HTMLParser.HTMLParser):
def __init__(self):
self.reset()
self.fed = []
def handle_data(self, d):
self.fed.append(d)
def get_fed_data(self):
return ''.join(self.fed)
def strip_tags(html):
#Warning this does all including script and javascript
x = MLStripper()
x.feed(html)
return x.get_fed_data()
def match(s, reg):
p = re.compile(reg, re.IGNORECASE| re.DOTALL)
results = p.findall(s)
return results

f = '/Users/tomsmith/Downloads/pdfextractor_1.csv'
lines = open(f).readlines()
stopwords = open( 'stopwords-en.txt').read().split()
d = {}
words = []
for line in lines:
url = line.split(",")[0].strip()
text = line.split(",")[1].strip()
words = text.split(" ")
for word in words:

word = lower( word )
word = match(word, "[a-z]*")[0]
print word
try:
float(word)
word = ''
int( word )
word = ''
except:
pass

if word != '' and word != '"\r\n' and word !='"' and len(word) > 1:
if word not in stopwords:
print word
try:
d[word] += 1
except:
d[word] = 1



finalFreq = sorted(d.iteritems(), key=lambda t: t[1], reverse=True)
out = open("tagcloud.csv", 'w')
out.write("word,frequency\r")
for item in finalFreq:
urls = get_urls( item[0] )
urls_str = "|".join(urls)

out.write(item[0] + "," + str(item[1]) + "," + urls_str + "\r" )
print item[0], item[1], urls_str

out.close()

I then uploaded it as a Google Spreadsheet and turned it into a Web Application.

Problems along the way


I found that displaying 1,000 words a bit of struggle for jQuery ( maybe I made it wrong ) so ended just showing 250 at a time.

I found, of course, that the tag clouding the word by popularity only revealed that the most popular words were fairly meaningless in this context like"foi" and "february" and "council". To do this properly you also need a list of contextual stopwords... some human intervention.

Another glitch was that occassionally, a FOI request would comprise of massive tables of suppliers ( in a PDF remember ) which would skew the words to be "B&Q" and "Wickes" or "building materials" etc. I had to avoid those.


The result


And here it is. A list of words that you can browse and find direct links to the PDFs from which it came. In the end, it seems that showing 250ish words is easier on the technology and the eye. The result is something that you could maybe browse and find a link to something relevant to you.



https://script.google.com/macros/s/AKfycbxAFOZjPBNnpg60MHzFQQjb2TkTsFUSDP_oPrRdNJDg83e3eCc/exec




Monday, October 15, 2012

Platform Dilemma ( UI Builder vs HTML application )

I'm currently working with the Chemistry dept. to help use Google Docs for recording students lab experiment marks.

It sounds simple enough until you find there are over 170 students in a year and there are at least 20 tests to be done, and the students get broken down into groups and rotated, and different people need to log different bits of information (that the student attended, their mark, that their mark has been agreed etc ). That's well over 2000 marks a year.... which in the scale of numbers isn't the biggest, I know, but that's not where the dilemma is.

Part of the problem is the different ways different people need to access creating the students marks but I'll come to that another day.

The dilemma is that, I have the need for a really simple to use interface for entering students' marks. I could either use the UI Builder built into AppsScript OR I could create an HTML application in AppsScript.

With an HTML application, it's a breeze to add jQuery ( a Javascript library ) which means I can lovely widgets such a calendar drop-downs or type-ahead scrolling ( where you start typing the students' name and it makes suggestions ) etc. jQuery really helps to make an interface user-friendly... and if you imagine that the lecturer may have to enter 30 at a time, you can see why it needs to be easy and quick.

With a UI Builder interface, there are fewer widgets available, but the Chemistry dept. themselves will ultimately be able to continue hacking anything I build as I work with them. It's very easy to just open up a UI Builder window and start adding things or moving them around whereas working with jQuery and HTML may be too big a leap into the geeky unknown.




Friday, October 5, 2012

The Day I Dropped Round The Security Guy's

After discovering that my direction of work for the Booking System was from a security perspective, deeply flawed, I thought that I could perhaps work around giving people access to the code by embedding a web application within a Google site. I thought this would be a big structural change, but it only took a few minutes. It looks like this.


There's a slightly different approach. Firstly the spreadsheet is embedded as view only. The spreadsheet is only used a visualisation of availability now - there's no direct manipulation of any data. 

Because, almost without thinking, I made the published web app a HTML based one, it meant that I could easily add jQuery and interface niceties like the date choosing dropdown (shown above).

Because all the code runs as me, and I've already authorized the code, the end user isn't presented with any awful dialogs. I make adding the booking something that the end user does, by hand themselves. You can pre-populate a Google Calendar new event form by hacking the url variables easily. Like this...



Conclusions?

Well, it's good to know that I have something that we can happily share around the university securely.

The disappointing aspects are that...

Google Spreadsheets don't seem to allow you separate the concepts of a "data editor" and a "code editor" in any useful way. That means, if you allow someone to add data, they can change the way that data gets added. The only way to "share" collaborative data is to cludge an awkward and ugly interface in the front of it. This means that, AppsScript is best used within a spreadsheet for individual ( or selected ) users... not in a collaborative scenario.

Whilst "jumping ship" into a HTML based web app, rather that using widgets in a Spreadsheet does add more complexity but also brings more control. I may end up not using the spreadsheet viewing gadget and build a table of bookings by scratch, one that can take direct manipulation to book multiple hot-desks across multiple days. I had wanted to leverage many of the spreadsheet's abilities, I now have to think of the spreadsheet as backend-data storage. I also have to grapple with jQuery to build a table as complex as a the spreadsheet shown above (with some cells coloured in). 

When working with the UI Builder, it isn't possible to have copy-and-pasteable UIs or share them amongst different spreadsheets. This makes for unmaintainable code, with each spreadsheet containing its own hand-crafted UI. Were UIs standalone objects, like AppsScripts are becoming, then they could at least be duplicated, or loaded and copied-n-pasted. 

Google's whole "Publish Your Apps Script" concept is flawed. Who would be daft enough to click the dialog that says, give this unknown author complete access to all my docs and emails? 

What All Of This Means

Over the last few months we've seen a lot of new developments from Google, many of them very welcome, but new developments are often very easy self-contained concepts... I would like to see a bit more depth and sophistication from Google in how the bits fit together, where the gaps are and where things could be smoother.

For example, the UI Builder components could include a date-picker surely? And what about type-ahead scrolling perhaps. And how about different classes for working with data, instead of SpreadsheetApp how about CodeAuthorSpreadsheetApp ( which can't read all the user's data  )? 

The feeling that I'm getting is that AppsScript ( and UI Builder widgets ) when used with spreadsheets is only useful for data that you own... and isn't appropriate for shared data... a web app front-end to working with shared data seems much more sensible ( but a fair bit more work ). It means that only the hot-desk admin team get to do the more complex things, but hey... it sort of makes sense.

Google really could do to take a look at HyperCard and make it work in AppsScript. It would solve a million problems ( yes, they're mostly all mine, but ... ).

My next challenge is to help develop a better way of storing students marks than the system that is currently used. This seems already to be breaking down into a spreadsheet with UI Builder widgets to do certain admin tasks and a web app for exam markers to record results. 

I'll let you know how I get on and share the code/files from the Booking System when I've got it running smoothly.













Thursday, September 27, 2012

The Day The Security Guy Dropped By...

It's always a pleasure when Arthur the online security guy at York drops by for a cup of tea. Today he pointed out, kind of him to bother really, that....


When you run an AppsScript in a Google Spreadsheet, it is run by the ActiveUser i.e the person that is logged in and working with the spreadsheet. In order to run the AppsScript, which edits the spreadsheet, you need Edit permission on that spreadsheet.

Stay with me.

Because you've got Edit permission on the spreadsheet, the container for the AppsScript, you've also got Edit permission on the AppsScript. That means, that you ( the ActiveUser ) can edit the script to say... get a copy of all my Documents ( assignments etc ) and upload them to a homework cheating site over here... and do it from your actual email address. It could send rude messages from you, the ActiveUser.

AAAAAARGHHHHH!

It's a massive security hole.

You could lock down the spreadsheet so that users can't edit the cells, and give them View access, but if you do that, then any menus ( which load the interface that makes changes ) don't load and so you don't get to be able to add data by proxy as it were.

If this route, of selectively locking bits of the file was almost possible, my old method of using a Task Queue that ran once a minute would mean that all the permissions, rather than being about the ActiveUser, would be tied to what's called the EffectiveUser ( the person who wrote the code and started the triggers that calls the code ).

Hang on, even I'm losing it now.

At this point, I thought... hang on... I can put MOST of the code into a standalone script library. In this way the ActiveUser would only be able to edit the code that displays the user interface. Oh. Still not right, because at that point naughty hacker could add anything they want.

And you see there is the problem. In order to do anything with this spreadsheet we're both looking at, you pretty much have to give people access to Read/Write all spreadsheets. Regardless of how innocuous the thing you are trying to do, the ActiveUser will be presented with an authentication dialog that looks like this...


And it says, "Only authorize the script if you truly trust the author".... Truly trust? Truly? Madly? Deeply? I don't even truly trust myself... how can I make a decision on that?

So basically, my dreams of an organisation creating and sharing solutions only work, if by sharing you mean...

You can take a copy of this data for yourself, and run the scripts on what is now your data

... what this doesn't mean is that...

We can work on the same data, using shared code and do anything useful with it.

All I wanted, he sighed wistfully, was to be able to collaboratively fill in a spreadsheet, using a slightly better interface than the formula bar but in order to do that the ActiveUser ( for those still paying attention, that's YOU! ) have to click a dialog that says you truly trust me, with all your data, email, calendars etc.

It's not going to happen is it?

In this case, it would be easily fixable if I could make the Scripts in a spreadsheet have the permission for you to run ( and maybe even read ) them but not to be able edit them. Or maybe I could say that I only want to edit THIS spreadsheet, and not have write access to ALL YOUR SPREADSHEETS!

As ever, permissions come to bite us in the arse. Ouch.

p.s I wonder what the hell Google are thinking with regards to all the AppsScripts/WebApps like these that are appearing in Chrome AppStore, which seem to also have a "truly trust" dialog in them, and none of which I have yet dared to run. Would you?

p.p.s Arthur's "solution" is to write the whole thing as a standalone web app, but, from a philosophical point of view I wanted to create solutions that other people could take and evolve to suit their needs. And also, writing a web app is quite hard.

















Tuesday, September 25, 2012

6. Booking System and Permissions (Update)



  • The original idea was to use a calendar for hot desk ( or perches as they're called ) bookings, that students could add their bookings to.
  • The idea was to use a spreadsheet, to essentially show which hot desks ( or perches ) which were already booked.
  • The idea was for the script to add an event to the booking calendar, and add the student to the event as a guest.


All of these lovely ideas would mean that there was one central calendar that admin people could check, that people could add their own bookings and also receive something in their calendar so they wouldn't to forget to show up.

Except, none of this works...

... or rather, because I was cornered into creating a Booking Task Queue sheet because adding events didn't work reliably I therefore ran the event adding code from a Trigger ( once a minute ) rather than as  it happens. This of course means that the script runs as ME ... the script author and not THE STUDENT ... who is using the booking system.

What this then means is that when the student makes a booking, they need to agree via a big ugly authentications dialog that they agree to have ME tinkering on their behalf.

What all this means is that if I use a central calendar I would need to add the student to a Google Group and make that Google Group able to manage all events on that calendar in order to add stuff to that calendar UNLESS I revert back to using a triggered script.

Are you still with me?

It would seem that I need to have TWO separate ways of adding events ( one via a trigger and the other when the student makes a booking) and work with calendars independently ).

That is to say, when a student makes a hot desk booking...

  • An event is added to the students' calendar using... CalendarApp.getDefaultCalendar()
  • An event is added to a task queue which will ultimately use ... CalendarApp.getCalendarById('YOUR_CAL_ID@group.calendar.google.com'); 

This means that as well as having a spreadsheet, that is effectively uncoupled from the calendar which it represents, I also have a central calendar that is completely uncoupled from each an every students' calendar. This means that were I to delete a booking in the central calendar, then the student would not be informed that this had happened.

Does this even matter?

I knew when I started using a spreadsheet as a pseudo-calendar, that it might be a bit of work to "sync" either what was in the calendar with what was in the spreadsheet and vice versa, but I'm now in the position of not actually needing a central calendar, I mean, what's the point of it exactly? I guess the answer to that is different presentation views ( week, day, agenda etc ).

But this get's less useful the more bookings there are ( which was the original need to even begin this project ) because as soon as you have 30 or so booking on one day, the calendar interface starts becoming useless, it's just too jumbled.

So the spreadsheet should become the golden master really.... there shouldn't be a central calendar and maybe that will clear up any permissions issues ( it will at least remove some of the icky corners of the code ) along the way.

Uh oh!

There are now some oddities with regards Google Drive and Google Groups. I think I'll save those for another post... maybe later.









Monday, September 24, 2012

5.0 Building a Booking System With Google Apps

I think I have a booking system that is close to working. A big leap forward was made when someone on the Google Apps message boards shared a way of avoiding the painful timeouts and false error messages after creating a calendar entry.

You basically create an event, getting its ID and then get it again, using that ID rather than working with an object. Who'd have thought? Anyway, this is the code that works.


 var cal = CalendarApp.getCalendarById('YOUR_CALENDAR_ID@group.calendar.google.com');
   var eventID = cal.createEvent(title, startDate, startDate).getId(); // create/get event ID

   //recall the event for each element you want to add
   cal.getEventSeriesById(eventID).setDescription(eventDesc);
   cal.getEventSeriesById(eventID).setLocation(location)
   cal.getEventSeriesById(eventID).addGuest(email)
   cal.getEventSeriesById(eventID).addEmailReminder(30)

This workaround has meant that I could do away with the Task Queue sheet, which was becoming more complex than it needed to be. It looks pretty much looks the same and works quite well.



Caveats

I would say that I have a few concerns about how fast the interface works. Sometimes it feels like an age to open the "Booking dialog" above. And once you click the "Book this perch" button, there is a visible delay as the cells get filled in ( each cell needs to look up to see which row it is in, and which column, but this shouldn't be too arduous a task ).

I'm also a bit worried about the need for a GREAT BIG AUTHENTICATION dialog that scares the hell out of you with its a. size, b. big red border and c. ugly HTML. You only have to grant access to it once but still... after granting access to my script to add a calendar invite to your calendar, you then have to click "OK"... and THEN you can go back to where you were and do it all again ( this time with no big ugly dialog). It's awful. Look.



I have a few niggling doubts about permissions too. For example, in order for anyone to do anything useful ( adding data to the spreadsheet ) for example, I think you pretty much give them access to everything else. Of course in a utopian dreamworld, that means that anyone using the booking system can also speed up my code and improve things a bit. I look forward to that happening.

I have irksome niggles about how an why the onOpen() script sometimes works, installing the "Booking..." menu and sometimes doesn't.

In the process of making this I've also found a suspicious side-effect loophole of working with Google Groups. My intention was to be able to share this Booking System with a collection of students, adding them to a group "silently" email-wise... this doesn't work... more on this later.

I don't trust cats either.

I will make a copy available once I've tested it a bit more.







Friday, June 1, 2012

Creating "Homework" Google Sites

Tom Stoneham came to us with an interesting problem... "Can I automatically create 80 or so HomeWork Google Sites from a template for students? And when the deadline has been reached can their access be revoked and links sent out to examiners". The students' task will be create a site about a particular philosopher. The prototype looks like this...



I'd had a stab at solving this earlier to see it was possible, and maybe too quickly I jumped for python. But in the spirit of making something that a. worked, b. was sharable, c. I wouldn't have to maintain ( hopefully ), I thought I'd have a go a re-doing it in AppScript.

Having met with Tom, there were a few addition requirements:

  • Can student sites have unique IDs that are mapped on to a marking sheet?
  • Can the URLs be kept in a list because, if you have 80 students then 8 markers may be given 10 students each?
  • What is the best way for the University of keep the snapshot but still give the student the ability to take their work with them? The student may even continue working on it.
  • Can an Editor of a site (i.e not the Owner) make a copy of a Site?
  • Just as an afterthought, can there be guidance about Copyright etc?
So, my early experiments were this.

1. Create the Spreadsheet



The siteurl column would be used to store the site created's URL for use later.


2. Create A Menu To Do Stuff

In the Script Editor I added:



function onOpen() {

  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var menuEntries = [ {name: "Create Sites...", functionName: "create_sites"},
                      {name: "Add Students To Sites", functionName: "add_students_to_sites"},
                      {name: "Email Examiners", functionName: "notify_examiners"} ];
  ss.addMenu("Administration", menuEntries);
}



After a while, I hit this wall...,  when creating a site, because it can take an elastic amount of time, but the code continues. That means if you try to create a site, then set up who the users are, the site might not be there yet. My workaround was to create separate functions... so the process, and indeed the Menu Items are...


  1. Create all the Google Sites based on the spreadsheet data, copying the chosen template site
  2. Add the students as Editors to the the sites 
  3. When the deadline is reached, remove the students as Editors ( making them Viewers ) and also make the Examiners Viewers ( sending them emails with the sites they have to mark in with each students unique ID.

So...



function create_a_site( template_site_name, student_unique_id ){
  var domain = "york.ac.uk" ;
  var template_site = SitesApp.getSite(domain, template_site_name );
  var name = "" + template_site.getName() + "-" + student_unique_id .toLowerCase() ;

  var title = name ;
  var summary = "Deadline 21st, December 2012" ;
  // See the warning in https://developers.google.com/apps-script/class_sitesapp about site creation speed
  var site = SitesApp.copySite(domain, name, title, summary, template_site);
  var sites_url = site.getUrl();
  return sites_url ;
}

function create_sites(){

  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName("students");
 
  var result = Browser.inputBox("Which Site is the template Site?", "project-template", Browser.Buttons.OK_CANCEL );
  if (result == "cancel"){
    //Browser.msgBox("CANCEL: " + result)
        }
   else{
     
     var range = ss.getDataRange().getValues();
     var students = rangeToObjects(range);
     var template_site_name = result
     
     try{
           
           for(var i = 0; i < students.length; i++){
             var student = students[i];          
             var site_name = template_site_name +"-" + student.uniquereference.toLowerCase();
             var domain = "york.ac.uk" ;
           
             var sites_url = create_a_site(template_site_name, student.uniquereference.toLowerCase() ); //Alter the values
             students[i].sites_url = sites_url;

             var x = i+2
             var cellnum = x.toString().replace("0", "");
             
             //Browser.msgBox(x);
             Logger.log( cellnum );

             var values = new Array();
             values[0] = sites_url
             var cellname = "D" + cellnum;
             var range = sheet.getRange( cellname )
             
             range.setValue( sites_url );
             
     }      
         
     }catch(e){
     Logger.log( e.message );
   }
     // Now write the URLs back to the spreadsheet. Or not.
     
   }
}

function add_students_to_sites(){
  //Browser.msgBox("Add Students To Sites!")
 
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName("students");
  var range = ss.getDataRange().getValues();
  var students = rangeToObjects(range);
 
  for(var i = 0; i < students.length; i++){
    student = students[i];
    var url = student.siteurl;
    var email = student.email;
    var examiner = student.examiner;
    var site = SitesApp.getSiteByUrl(url);
    site.addEditor(email);
    site.addViewer(examiner);

   
  }
  Browser.msgBox("Added students to sites")
}





function test_create_a_site(){
 var x = create_a_site("project-template",  "Y63326039"  );
 Logger.log( "Done! " + x );
 //Browser.msgBox(x)
}

function add_people( site_name, student_email, examiner_email ) {
  var domain = "york.ac.uk" ;
  var site = SitesApp.getSite(domain, site_name );
  site.addEditor( student_email ).addViewer(examiner_email);
  the_url = site.getUrl();
 
  //email a link to the student
  /*MailApp.sendEmail(student_email,
                    "Your Philosophy Homework Site",
                    "A Google Site has been created for you to fill in. \n\n " +
                     the_url + "\n\n",                  
                    {name:"Philosophy Course"});*/
 

  Logger.log("Done!");
 
}

function notify_examiners ( ){
  // email a link to the examiner
  // https://developers.google.com/apps-script/class_mailapp
 
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName("students");
  var range = ss.getDataRange().getValues();
  var students = rangeToObjects(range);
 
  for(var i = 0; i < students.length; i++){
    student = students[i];
    var url = student.siteurl;
    var email = student.email;
    var examiner = student.examiner;
    var site = SitesApp.getSiteByUrl(url);
   
    MailApp.sendEmail(examiner,
                      "TEST: A Philosophy Homework Site To Mark",
                      "A Google Site has been created for you to mark. \n\n " +
                     url + "\n\n",                    
                    {name:"Philosophy Course"});
    //At this point we might want to record that the mail has been sent...
     
  }
 
  Browser.msgBox("Done!");
}
 

Conclusions

There are still a few bugs to iron out. It really doesn't like creating a site if a site already exists with that name, and I've found that deleteSite() never runs smoothly.

The next step is maybe to add some UI to select which site you want to use as a template. And of course to start making it a bit more robust. Ahem.

I'm actually quite surprised that this was easier to achieve in AppScript than it was in Python... probably...






Thursday, May 31, 2012

4. Building a Booking System with Google Apps ( Code )

Note: This is the 4th of 3 previous posts about hacking Google Apps to attempt to create a usable Booking System.

First run this code from the Script Editor. It will make you a "Calendar Sheet" with X number of items as columns and Y dates as rows.

function create_a_blank_calendar_sheet(){
  // Run this from the Script Editor to create a Calendar Sheet.
   
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.insertSheet("Calendar"); //this'll fail if there is one already...
 
  var result = Browser.inputBox("How many x items", "e.g 10 or 25 etc", Browser.Buttons.OK_CANCEL );
  if (result == "cancel"){
    //Browser.msgBox("CANCEL: " + result)
        }
   else{
     //headers?
     sheet.insertColumnsAfter(1 ,result);
     for(i = 2; i < result; i++){
       ss.setColumnWidth(i, 18);
     }
   
     var days_result = Browser.inputBox("How many days", "e.g 365", Browser.Buttons.OK_CANCEL );
     // dates down the sides
     var n = 1;
     for(i = 2; i < days_result; i++){
        var now = new Date();
        now.setDate(now.getDate() + n);

        day = now.getDay();
       sheet.getRange(i,1).setValue(now); // You can format the column without times yourself :-)
     
       //colour the weekend's background
       if (day ==6 | day == 0){
        sheet.getRange(i, 1, 1, result).setBackground("#d6d6d6");
       }
        n++;
     }
   }
 
}
 
Now some code to add an "Administration" menu that people can use to click on a cell and book a "something or other".

function onOpen() {

  var ss = SpreadsheetApp.getActiveSpreadsheet();
  //var sheet = ss.getSheets()[0];
 
  //{name: "Create A Calendar Sheet", functionName: "create_a_blank_calendar_sheet"}, // This is just for setting up.
  var menuEntries = [
                    {name: "Book this perch...", functionName: "book_perch"}, ]
  ss.addMenu("Administration", menuEntries);        
                   
}

This is a bit of code where you name your column names. You might want to do this by hand, with "Apples, Oranges, Pears, Kumquats" etc.

function setup_headers(){
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName("Calendar")    ;  //Yours might be something else
 
  /*setup column names. Probably wise to stick to single words? I'm using p17, p18 etc.


  var number_of_columns = 70
  for(i = 2; i < 
number_of_columns; i++){
       sheet.getRange(1,i).setValue("p"+i);
   }*/


}


Now these two functions do the work of creating an Event in an actual calendar and marking the spreadsheet to say it has been booked.

function add_to_calendar(perch, str_date){
    var user = Session.getUser();
    var email = user.getEmail();
 
    var cal = CalendarApp.getCalendarById('york.ac.uk_5e8b7i5**************google.com');
    Logger.log (cal.getName() );// Just check you got the right one
   
    date = new Date( str_date );
    //weirdo hack date help! help! All day events always end up a day behind?
    date.setDate(date.getDate() + 1);
     
    var event = cal.createAllDayEvent( email, date );
    Utilities.sleep(6000); // This is so that Google Calendar can "catch up". Seriously.
                   
    var msg = "Perch " + perch + " booked on " + date + " for " + email + "." ; // So is this...
    Browser.msgBox(msg);
    Utilities.sleep(6000); //And this...
    try{
      event.addEmailReminder(60) ;
      event.addGuest( email ) ; // This adds it to their calendar as a request.
      event.setLocation(perch);
    }
    catch(e){
       Logger.log(e.message);
    }

  return 0 // All is OK
}
   
function book_perch(){
    // This is called from the Menu
   var user = Session.getUser();
   var email = user.getEmail();
                   
  var ss = SpreadsheetApp.getActiveSheet();
                   
  // work out which cell is selected. Probably need to work out IF it a "live cell"
  // or if it is booked already, if it's one of their bookings.
   
  var cell = ss.getActiveCell();
  var cellname = cell.getA1Notation();                    
  var range = ss.getActiveRange();
  var row = range.getRow();                    
  var colindex = range.getColumnIndex();
                   
  // get the header name, e.g "p34"                  
  var perch_name = ss.getSheetValues(1, colindex, 1, 1)
  // get the row date value                  
  var date = ss.getSheetValues(row, 1, 1, 1)
                   
  var error = add_to_calendar(perch_name, date);  
  if (error == 1){
     Logging.log("Something went wrong");
  }else{
      // Only fill in the cell if the Calendar hasn't failed.              
      cell.setValue(email) ;
      cell.setFontColor( "#ffffff" );
      cell.setBackground( "#990000" );
   }
}

And there it is. It'd be true to say, there's more that it doesn't do than it does. For example, if I delete an item created in the calendar ( for real ) the spreadsheet doesn't automatically make the slot available again, but this is doable with a Triggered check on each item.

It's an encouraging start though. My only nagging doubts about how to deal best with the delay between creating an event and being able to then manipulate it, because the code continues executing but the documentation says it can be minutes before your event appears in the Calendar. At the moment I'm just lobbing in a few delays() ... but that's not right is it?







 

© 2013 Klick Dev. All rights resevered.

Back To Top