Showing posts with label Apps Script. Show all posts
Showing posts with label Apps Script. Show all posts

Wednesday, October 29, 2014

Creating a Documentation Process With Google Forms, Documents and Spreadsheets.

We wanted to improve the way people at the University request new software and tools. This is a process that requires lots of people's feedback and needs to be very flexible. We need to get software experts to look at it, security teams, the support teams, teaching experts to see if is a good pedagogical match. We need the licensing to looked at and the usability and accessibility. The list is astonishingly long and in these cases it often gets so that your process map just starts to look like infinite spaghetti. No wonder it didn't quite work, infinite spaghetti is always troublesome.

Much of my work involves trying to find a workable solution to a fiendish problem.. it's simplicity hunting. And when working with people around the university it's clear that they really don't want a tool that solves their immediate problem, they want abilities that solve problems like these. This is a very different thing. And besides I personally couldn't create

So, out of necessity I created (an as yet, unfinished ) Apps Script code library, to try and make doing jobs like the one above simpler. The point of this library is not to do anything fancy or specific but just to do those things that frankly Google should have rolled in as features anyway so that new coders could easily just wire their app together with a whole heap less complication.

The code library is called Handy Lumps because that's just what it is. Handy Lumps of code that you can re-use again and again. I won't tell you how to install Handy Lumps library into your code, but you can find that out easily enough. The project id is...

1ykOx87hMWudgdOl3i9XND-zeV8WEieBjVwxcPYG_2iDvX5kd70KpbfIl

So What Does This Example Do, Tom?

In this example, someone fills in a Google Form to request some new software. What it then does is take that information and render it into a Google Document template file, and put it into a folder. Lastly, it saves the URL to the new file in the spreadsheet. It's amazing how many use cases look a bit like this.

It's also amazing how many processes start by looking like something mappable, something with a clear structure but actually are closer to an iterative collaboration. For example, the template that gets created has further questions in, which can of course be added to and bent into the shape that is required. And of course using Google Document +commenting feature you can easily bring someone new into the discussion for their advice and help.


So How Does This Example Work, Tom?

Let's look at the code. First I created a Form and then went to Spreadsheet and chose the Tools > Script Editor menu and added this. I'll explain what it does below.


function onFormSubmit(e) {

  //Get the values in a nice Array
  var values = HandyLumps.row_to_dict(e.range)
  var template_id = "TEMPLATE_DOCUMENT_ID"
  var folder_id = "FOLDER_ID" // Our Responses folder.
  var name = values['Name'] + " - " + values['What software are you requesting?']


  // Create a Google Doc
  var new_file_id = HandyLumps.copy_and_render_to(template_id, name, values, folder_id)
  var new_file = DriveApp.getFileById(new_file_id)
  var url = new_file.getUrl() 

  //Update the spreadsheet with a link to the new file
  var ss = SpreadsheetApp.openById("THIS_SPREADSHEET_ID")
  var sheet = ss.getSheetByName("Form responses 1")//this sheet
  var row = e.range.getRow()
  sheet.getRange(row, 16 ).setValue(url)
 

  MailApp.sendEmail("YOUR-GOOGLE-GROUP-HERE@york.ac.uk", "New Software Request: " + values['What software are you requesting?'], url, {noReply:true})

}

The first thing the script does is turn the row of data into a nice array. This returns an array that looks like this {'timestamp':2014/29/10 10:55:45, 'name': Tom ...etc} It builds this array based on your header names ( and yes, it assumes they are unique for simplicity ) . Doing this avoids the issues with e.namedValues containing multiple items and gives me a simple array I can use later.

Next we tell the script the ids of the template document and into which folder we want the new documents to go.

We then create a new document from a template file. The template file has {timestamp} and {name} tags in which match my spreadsheet headers and get replaced with the values. To do this we use ...

HandyLumps.copy_and_render_to() 

This function returns the id of the new document created, so we then open it with DriveApp and get its URL. ( I did think about returning the File object, but often that's not what I needed anyway so decided on the simplest thing ).

I then use regular Apps Script to save that URL into the same row.

The last line mails a Google Group to let them know a new request has come in.

Ta Da!

There you have it. We've made quite a cute thing in a paragraph of non-scary code copy-and-pastee-style.

I'm all for the current trend to believe that "we all can be coders now" but I also think that the tools themselves could be made a damn sight easier to use before we welcome those brave souls willing to give it try.






Next Steps


More involved versions of the above code create a Google Doc from a template that has code in it, so that new document can show a sidebar ( for example to approve it, or give it a mark out of ten ) that let's someone move the document onto the next step. The data from the sidebar is of course saved into the right row using Handy Lumps functions like this...

HandyLumps.get_row_containing(ss_id,sheet_name, column_letter, match)


In the example above, a document's script might contain...

var doc = DocumentApp.getActiveDocument()
var doc_id = doc.getId()
var result = HandyLumps.get_row_containing("YOUR_SHEET_ID","SHEET_NAME", "M", doc_id)
var row = result[0]
var values =result[1]


... which essentially means that a document knows where to store its new data. And using cute things like Google Document's Named Ranges you can make a sidebar that stores people's textual contributions back into the original spreadsheet. I'll hopefully get to sharing that stuff later.










Friday, June 27, 2014

Gmail Scheduled Send Email

Someone recently asked for the ability to schedule send an email. It was for a link to a class feedback form and ideally they'd like to be able to send it near the end of the class.

We looked at Boomerang, which had issues with authenticating with the York domain, and broke the CSS of Gmail slightly. We tried mx Hero, a Chrome app but after a tip off from a colleague and reading the reviews and discovering it at times didn't send the email, or used http rather than https etc we thought it might be better to look into making our own solution.

Apps Script to the rescue!

I wanted something that was really simple, and easily editable to do what you want.

I made an Apps Script to check your Gmail Drafts folder. If there's a message whose subject begins with a date, like this, it sends it ...

[27/06/2014] Hello, this is a message from the future!


How to install it...

1. Go to the Apps Script here and choose the menu, File > Make a Copy. You will want to change the admin_email to your email address (line 32) so that you get a BCC copy of any mail sent out. I have set the emails to come from noReply. You can also change that in line 82.

2. Now, to authorise the script to run, click the Run button and then click OK. This is Google making sure you want this code to be able to be run.



3. From the menu Resources > "Current project's triggers" and create a Trigger that will run the check_drafts() function every 5 minutes or so.  See below....






4. Now you are ready to go. All you now need to do is create draft emails that have title with a date EXACTLY in the format above... All you then have to do is close the email... and wait. It might not send at exactly the time you specify, depending on how often your Trigger is set to run, but it'll be close.





Every time the script runs, it looks for messages that have a date in square brackets. It makes a copy, BCC-ing in the email you specified ( this is important because otherwise it doesn't keep a copy in your Sent folder... you need a copy to make sure it has indeed been sent ). 

The Script creates a label called "Scheduled Gmail Sent" and moves sent drafts to the bin ( unfortunately, Apps Script won't let me send the mail so it appears in the Sent tray or delete it outright ). Maybe you can work out how this bit works.

I couldn't get inline images to work, but attachments work fine.

Remember your Google Gmail quota is 500 a day, but a message with 300 people BCC'd in counts only as one message.







Friday, November 15, 2013

Creating a Process Workflow with Google Spreadsheets

I thought I'd share this ongoing project I've been working on about creating workflow support for a team. I've referred to it before in a blog post called Using Spreadsheets Instead of Forms, in which I argue how using the commenting, "live saving", versioning and collaborative features of spreadsheet far exceeds what can be done with a simple Google Form (see below). This is especially so when the data you are collecting is long and complicated. ( Tip of the hat to Tim Saunders who had this idea ).



Having quickly trialled and liked bespoke workflow apps like Kissflow, and also read the documentation for bigger workflow systems like RunMyProcess, we realised that one of our first challenges was to define the mood or tone of this process. It was clear that the process we were trying to support was more consultative and discussion-based than a hard passing of numbers and approvals in a clear process flow. Our process needed to be more about "letting the right people know where they are up to" and asking for input than a mechanistic model.

As we worked we realised that the flow itself was remarkably simple and was mainly about making documents available to people for commenting and letting teams of people know what the status is.

We realised that we could break most stages of the process into simple interactions and added them to the spreadsheet as menu items, for example, "Submit initial request" or "Submit to Team B" or "Reject request" etc.

These menu items do three things:

  • Show a dialog telling the user what they were about to do
  • Sent emails to the relevant people and groups
  • Updated the project's status in a central spreadsheet
And to help us to speed up the process of development, we created a Processes spreadsheet that looks like this...


Each process gets rendered against the NamedRanges in the spreadsheet, so that {MainContact} becomes "Joanne Blogs" or whatever. Sometimes, even emails are rendered this way like {HeadOfDepartmentEmail} in the CC of the email that gets sent out.

Some special values had to be sneaked in such as {folder_link} and {link} which means that emails can contain links to the current document or GDrive folder.

Working this way has meant that fine-tuning who gets sent what is a LOT quicker, it not being hard-coded. It makes the authoring of those automatic emails which usually get sent to groups rather than people so that people can easily turn off email notification if need be. 

The menu items typically have code that look something like this...

function utc_approved( url ){
if (is_a_member_of( "planning-utc-controllers@york.ac.uk" ) == true ){
run_process( "Process:10", url )
set_status("UTC approved")
}else{
Browser.msgBox("Not allowed", "You need to be a member of the UTC controller group to run this", Browser.Buttons.OK_CANCEL)
}
Browser.msgBox("UTC approved" )
}

... and the run_process() function simply shows a dialog, renders and sends out emails and sets the status of the request. Some menu items have code that moves a folder to an "Archived" folder for tidiness sake, but nothing too complicated.

I think the thing I learned from this project, once again, is that, like Booking Systems, although they always present themselves at the door as a the same thing, they are always in a clever disguise and really are something very, very different indeed. And by the time they've got through the door and taken their coat off it's already too late.



















Wednesday, July 31, 2013

A Difficult Day With Google Apps ( Boo! )

Yesterday was a difficult day with Google Apps. Almost everything I tried to create was stopped dead in its tracks. I am making some spreadsheets that support an approval workflow process and so far it had been going really well.

I have a Google Form, that initiates the process. This keeps a list of the workflows. These are displayed in a web app. When the form is submitted, a copy is made of a master spreadsheet is made and the people fill in the data in that.

At various points of completion, a controller person fires off scripts like "Initial Approval", "Approved by the Teaching Committee" and the relevant people are notified that they now need to "fill in their bits".

So far so good.

I began thinking that, at some points it would be really useful to assemble the relevant data, notes, reminders and links into a Gmail draft so that the controller person might check it over, add any extra message and then send it. But with Apps Script you can't create a GMail draft message. Boo 1!

So I thought, OK, what if I use a Google Doc as a simple template with %SomeTags% in so that the people running this process could easily author these emails. The %SomeTags% in the document could be replaced with data from a Named Range in the their spreadsheet.  I discover that with with Apps Script there is no way to get a list of named ranges. Boo 2! 

Next, after experimenting with attaching scripts to buttons in the spreadsheet, that might say "Approve" I thought I'd have a go at cleaning up the interface by putting all the workflow actions in tidy menus with sub menus. I discover that you can't create sub-menus in spreadsheet menus.  I take the argument that this might be better interface design but still, Boo 3!

So I then think that maybe I can show the menu items based on the user's memberships, so that people only get to see the menu items based on their needs. I think I discover that the user would need to run the onOpen() function and authorize it for it to work. Boo 4!

I then remember our security guy's advice that allowing people to edit spreadsheets AND scripts is a bit hokey. There is no way to allow people to edit spreadsheet data and not the code in them. Boo 5!

I then thought I'd create a "Home" sheet in my spreadsheet, which would lookup the workflows status from the list spreadsheet using the =ImportRange() formula. I discover that if I delete a row in the listing spreadsheet that ImportRange brings back the wrong status. It's sort of hard-wired to a particular cell. Boo 6!

What a day! And as Oliver Reed might utter, What a lot of boos!

Google do seem to be tightening up some of the consistency of their Apps Script features lately, with varying results. And they seem to be listening to "developers" like myself and producing tools that support what they want to do rather than features that look nifty in a demo ( like that one about copying a bullet list in a Google Doc straight into a Google Form multiple choice question ) but all of these boos are things that other people have asked and argued well for.

So come on Google, how about searching Stackoverflow for "Google Apps Script" and "Can't do that" and "according to the documentation". There's a wealth of common-sense and small but valuable features that people are expecting to be there.

But first can you get on and let me do this....

var ss = SpreadsheetApp.getCurrentSpreadsheet()
var ranges = ss.getNamedRanges()

That's not a lot to ask is it?







Monday, June 24, 2013

Confusion about Apps Script Projects in Spreadsheets.


I'm in some confusion about how Apps Script projects work when embedded in a Google Spreadsheet. In my current spreadsheet, when I choose "Script Editor", I see this...



As you can see, there are multiple projects in the spreadsheet. I don't get this. It always happens that when I copy a spreadsheet too... that I end with a "Copy of XXX" and "XXX" Apps Script projects inside the spreadsheet. 

I can add extra projects via the "Create a new project" link but I can't remove projects from spreadsheets. This gets more confusing if both "Copy of Web App n Stuff" and the "Term Week Dates Booking Project" have a doPost () function in them. Which function gets called? 

Why would I want more than one Apps Script project in a spreadsheet?

Why can't I flip a project out to be a standalone Apps Script project?

When making copies of spreadsheets - why do I end up with multiple projects in a spreadsheet?

How might I remove an Apps Script project from a spreadsheet?



Wednesday, June 19, 2013

Converting a Google Document to simple HTML

This article looks at creating an application that converts a Google Document to HTML, saves it in a folder on your Google Drive and then shares that folder on the web.

This tool is meant for those situations where you have to work in HTML but you'd prefer it if you could use Google's fantastic collaboration features on the content first.

What This App Doesn't Do

It doesn't try to be too clever. I quite like HTML from back in the 90s when it was simple. Most other systems where you can add HTML don't like you getting too fancy either.

It doesn't try to size the images or even get ALL the document elements like Tables of Contents or Page Breaks, it just does the absolute basic needed to copy the source HTML into Blogger or into your CMS and you will need to do more than a little "fixing up" along the way.

What is really useful about this app is that all the images are now hosted and so you don't need to go through the painstaking process of copying the images into new images, saving them online somewhere, getting the URL of those images and replacing the right image src in your new HTML source. This was a pain for me.

Also, the app doesn't HTML encode the "<" and the ">" so you can add HTML tags like the preformatted tag or whatever, knowing that they'll still be there in the outputted HTML. I use the font Courier New to tell it that I don't want the converter code to touch it, but one of the problems is that ANY HTML that is lurking in "courier new" code is shown, as HTML.

The results can be hilarious. Ah well. I said it would need "fixing" up. Here's an example of the HTML it generates.

The application itself looks like this.


The simple application lets you select which Google Doc you want to convert to HTML and then loops through the document elements and creates a html page.

Any images found are added to Google Drive hosting ( their URLs changed to the new location ) letting you copy the HTML from the "index.html" file and paste it anywhere you need.

And Finally

You can try it on your documents here: https://script.google.com/macros/s/AKfycbyRt8FCEDMtZELx7-E_c3Hjktaaf1qJwRmitfRx48UocIP8ViBA/exec

Or if you'd like to rummage around the code please feel free to improve it.
https://script.google.com/a/york.ac.uk/d/1BWQRHrM589TnsXviuB3Zzws_ShFDF6sEdaO4sg4Eht0kuZx1iou4Xxn2/edit

Inserting A Google Doc link into a Google Spreadsheet

This article looks at using Apps Script to add new features to a Google Spreadsheet.

At the University of York, various people have been using Google spreadsheets to collect together various project related information. We've found that when collecting lots of different collaborative information from lots of different people that a spreadsheet can work much better than a regular Google Form.

Spreadsheets can be better than Forms for data collection because:

  • The spreadsheet data saves as you are editing.
  • If you want to fill in half the data and come back later, your data will still be there.
  • The data in a spreadsheet is versioned, so you can see who added what and when and undo it if necessary
  • The commenting features are brilliant - especially the "Resolve" button in comments.

One feature we needed was to be able to "attach" Google Docs to certain cells in a spreadsheet. It's easy to just paste in a URL into a spreadsheet cell, but they can often all look too similar and you don't know what you are getting until you have clicked it.

For example, one part of your spreadsheet might ask "Do you have any supporting files?" and it would be handy to be able to insert a link to any number of Google Docs which might include a project plan Google Document, a PDF letter of support and a budget spreadsheet.

Like this.


When you select "A file from Google Drive" a dialog is presented showing you your Google Drive files, like this.


In this example, I am selecting the "What goes where: Action Plan" file. A =HYPERLINK() formula with the name and link of this Google Document is then inserted into spreadsheet.

Adding More Abilities

Once I'd created the ability to attach a Drive document, I knew that people would ask for other obvious features.

1. Uploading a file directly from your computer

You may have noticed that the menu also allows you to "Upload a file from your computer". This simply uploads the document into your Google Drive and then links to that uploaded file.

2. Attaching a regular link

There is also the ability to add a simple "regular hyperlink" - we often have documentation in our wiki or on web servers that need linking to as well as GDrive documents. This tool provides a simple interface


Conclusions: Keeping It Simple

This tool doesn't try to make sure that the permissions on the attached Drive file match those of the spreadsheet, but that is a good thing and anyone without access to a particular file can request access the usual way.

I experimented with showing an icon of the file type, or a link icon if linking to an external site, but that made the code need to look in adjacent cells to see if the data was empty.

This code doesn't save the uploaded files into a particular folder because it would make the code less "copy and pastable" but you could easily add that feature.

I also thought it might be nice to add a cell comment to say who uploaded the file. Again, that wouldn't be hard to add yourself.

There is a little interface clunkiness with the UiApp which means that the Google Drive chooser dialog window is a bit cramped, but all the functionality works well enough to add the extra dimension of collections of related files to your spreadsheets. Hopefully by publishing this article I may get some help with this.

You never know, maybe Google will roll some of these features into the main applications, but until then you can use this addition to easily collect together references to various Google Docs in a spreadsheet.

Also, I wonder when Google will add a Script Editor, or the ability to add menus to Google Documents? ( Update: Since I wrote this, Google have now added this. Cool. )


How To Add This Menu To Your Spreadsheet

The code to add an "Attach..." menu to your spreadsheet is in this spreadsheet called "Inserting GDrive docs into a spreadsheet" here:

https://docs.google.com/spreadsheet/ccc?key=0Ajnu7JgRtB5CdGtoUmM1YnlHaS1KWVowVkxtMnFzWFE#gid=0



1. Make a copy of the spreadsheet.
2. Go to menu Tools >> Script Editor
3. Copy and paste the code into the Script Editor of another spreadsheet of your choice.
You will have to run the onOpen() function to get the menu to appear.

Creating a Restricted Multiple Choice Form With Apps Script

I was contacted and asked if I could create a Google Form where the user can only check 8 out of 16 checkbox choices. Google Forms can't do this sort of restriction and so I whipped together a quick web application to do this.
I created an Apps Script in Google Drive and then added this code below.

function doGet(e) {
var app = UiApp.createApplication().setTitle("Restricted Multiple Choice").setHeight(250).setWidth(500)

// Define the grid layout
var grid = app.createGrid(16, 3 ).setStyleAttribute(3, 2, "width", "420px").setCellPadding(5)
grid.setStyleAttribute("margin-left", "auto")
grid.setStyleAttribute("margin-right", "auto")
grid.setStyleAttribute("margin-top", "100px")

// Create the text at the top
var html = ''
html += "Restricted Multiple Choice"
html += "You can only choose 3 of the items below."
var html_widget = app.createHTML(html, false)
grid.setWidget(1, 2, html_widget)

// Create the checkboxes
var select = app.createServerHandler('selectHandler').addCallbackElement(grid)

var checkbox_1 = app.createCheckBox("Item 1").setId("item_1").addValueChangeHandler(select).setName("item_1")
grid.setWidget(3, 2, checkbox_1)

var checkbox_2 = app.createCheckBox("Item 2").setId("item_2").addValueChangeHandler(select).setName("item_2")
grid.setWidget(4, 2, checkbox_2)

var checkbox_3 = app.createCheckBox("Item 3").setId("item_3").addValueChangeHandler(select).setName("item_3")
grid.setWidget(5, 2, checkbox_3)

var checkbox_4 = app.createCheckBox("Item 4").setId("item_4").addValueChangeHandler(select).setName("item_4")
grid.setWidget(6, 2, checkbox_4)

var checkbox_5 = app.createCheckBox("Item 5").setId("item_5").addValueChangeHandler(select).setName("item_5")
grid.setWidget(7, 2, checkbox_5)

var checkbox_6 = app.createCheckBox("Item 6").setId("item_6").addValueChangeHandler(select).setName("item_6")
grid.setWidget(8, 2, checkbox_6)

var checkbox_7 = app.createCheckBox("Item 7").setId("item_7").addValueChangeHandler(select).setName("item_7")
grid.setWidget(9, 2, checkbox_7)

// Create the "convert" button
var handler2 = app.createServerHandler('submitHandler').addCallbackElement(grid);
var convert_button = app.createButton('Submit Selection', handler2).setId("btn")
grid.setWidget(10, 2, convert_button)

// Create the message at the bottom
var msg = app.createHTML("Please make only three choices.", false).setId("msg")
grid.setWidget(11, 2, msg)


app.add(grid);
return app
}
function submitHandler(e){
var app = UiApp.getActiveApplication( )

var msg = app.getElementById("msg")
msg.setVisible( true )
msg.setText( "This doesn't do anything yet" )

return app

}
function selectHandler(e){
var count = 0
if ( e.parameter['item_1'] == 'true' ){
count = count + 1
}
if ( e.parameter['item_2'] == 'true' ){
count = count + 1
}
if ( e.parameter['item_3'] == 'true' ){
count = count + 1
}
if ( e.parameter['item_4'] == 'true' ){
count = count + 1
}
if ( e.parameter['item_5'] == 'true' ){
count = count + 1
}
if ( e.parameter['item_6'] == 'true' ){
count = count + 1
}
if ( e.parameter['item_7'] == 'true' ){
count = count + 1
}


var app = UiApp.getActiveApplication( )
var msg = app.getElementById("msg")
var btn = app.getElementById("btn")
if ( count > 3 ){
btn.setVisible(false)
msg.setText( "You have chosen more than three items" )
}else{
btn.setVisible(true)
msg.setText( "You have chosen " + count + " items." )
}
return app

}



The application itself looks like this, the submit button is only clickable once you've clicked three checkboxes.
The resulting application is here, give it a whirl.
https://script.google.com/a/macros/york.ac.uk/s/AKfycbwczyQumF8qax2HGoZt9K-RzzY7ItWdKSvyH_aQq2p-PQ9vXNiF/exec


If you'd like to make a copy of the application's code to figure out how it works, the Apps Script code is here:
https://script.google.com/d/1h3PsyWZ2qNxyTDpDPP4__f-_0LjhysLti9NmHMRUCB6ktB2nrcPgvxYT/edit?usp=sharing


In Conclusion

This unusual little app is one of many that I'm currently exploring. Lots of people believe that what they want is a simple form for gathering data, but are finding out that they want the data to be "intelligent" and dependant on other items selected.

Just because it walks like a form, looks like a form and quacks like a form, doesn't meant it's a form.




Writing a Simple QR Code Stock Control Spreadsheet

At Theatre, Film & TV they have lots of equipment they loan to students, cameras, microphone, tripod etc. Keeping track of what goes out and what comes back is a difficult job. I have seen a few other departments struggling with the similar "equipment inventory" problems.

A solution I have prototyped uses QR codes, a Google Spreadsheet and a small web application written in Apps Script. The idea is, that each piece of equipment ( or maybe collection of items ) has a QR code on it. Using a standard and free smartphone application to read QR codes, the technician swipes the item and is shown a screen that lets them either check the item out or return it.

The QR app looks like this.



The spreadsheet contains a list of cameras. It has links to images and uses Google Visualisation tools to generate its QR codes. The spreadsheet looks like this.


The Web Application

The web application, which only checks items in or out and should be used on a phone in conjunction with a QR code scanner iphone app, like Scan.
The application is written in Apps Script looks like this below. It shows the current status of that particular camera and lets you check a camera out.


Building This Application

There's a strange "chicken and egg" situation about building this application. In order to create a QR code that would lead to an individual camera, you first need to "Publish" your web app so that we have the URL to your application. Once published we can use the URL for our application and start populating the spreadsheet with cameras.

Populating the spreadsheet

First, I added some URLs to pictures of cameras, and titles and descriptions to my spreadsheet.
Next, in the spreadsheet in cell C2 you can see the formula...

=CONCAT("https://script.google.com/a/macros/york.ac.uk/s/YOUR_URL_GOES_HERE/exec?id=",A2)

… this formula creates the URL that is appended with "?id=231" etc and will be used to generate a unique QR code image.
In cell D2 I have the formula …

=make_QR(C2)

… this formula calls an Apps Script function that looks like this...

function make_QR( url ) {
/*
Note: If you add a number to the domain, as per the documentation, the https starts erroring. I think.
Note: URLs are limited to 2K in length
From: https://google-developers.appspot.com/chart/infographics/docs/overview
*/

var size = 150 // The height and width needed.
var encoded_url = encodeURIComponent( url )
var image_url = "http://chart.googleapis.com/chart?chs=" + size + "x" + size + "&cht=qr&chl=" + encoded_url
return image_url
}

… this takes our unique URLs and uses Google Visualisation Tools to create an image for the QR code, one of which looks like this.

We can display the image in the spreadsheet using the formula...

=Image(D2,1)

Deprecation Warning!

Although the Charting service is being deprecated, it's probably good to use until April 2015. See https://developers.google.com/chart/terms. I imagine it would be easy to generate and download all the QR code images you need, finding an alternative QR creation API in a few years time.

Creating The Web Application

The first part of the application gets the data from our spreadsheet, finding the relevant row. It then builds an application using UiApp objects for the values we want to display.
function doGet(e) { 
if (typeof e.parameter.id == 'undefined'){
return no_id(e) // The URL doesn't have an ?id=345 on the end!
}

var id = parseInt( e.parameter.id ) // This is the id of the row in the spreadsheet.

// Get the data from the spreadsheet and get the row that matches the id
var this_spreadsheet_id = ScriptProperties.getProperty('this_spreadsheet_id')
var ss = SpreadsheetApp.openById(this_spreadsheet_id)
var sheet = ss.getSheetByName("Sheet1")
var range = sheet.getDataRange()
var last_row = range.getLastRow()
var last_column = range.getLastColumn()

for(i = 2; i <= last_row ; i++){
var this_row = sheet.getRange(i,1 , 1, last_column)
var values = this_row.getValues()[0]
var row_id = parseInt( values[0] )
if ( row_id == id){
var title = values[5]
var details = values[8]
var status_txt = values[7]
Logger.log( "STATUS: " + status )
var image_url = values[4]
}
}

// Create an application
var app = UiApp.createApplication().setTitle("Check in/out").setHeight(250).setWidth(400)

// Create the layout
var grid = app.createGrid(8, 3 ).setStyleAttribute(3, 1, "width", "420px").setCellPadding(5).setBorderWidth(0).setId('grid')
grid.setStyleAttribute("margin-left", "auto").setStyleAttribute("margin-right", "auto")
grid.setStyleAttribute("margin-top", "100px")


var image = app.createImage(image_url).setWidth(100).setHeight(100).setStyleAttribute("margin-left", "auto").setStyleAttribute("margin-right", "auto")
grid.setWidget(0, 1, image)
... and so on... and so on ...


We then need to create the buttons, like so...

  // Check in button
var handler = app.createServerHandler('check_in').addCallbackElement(grid)
var check_in_button = app.createButton('Check in', handler).setStyleAttribute("font-size", "24px")
check_in_button.setId("check_in_button")
grid.setWidget(4, 1, check_in_button)


… Later in the code we create a function ( or handler ) for the button. It updates our spreadsheet with CHECKED IN or CHECKED OUT, and also updates a few interface elements like this....
function check_out(e){
var id = parseInt(e.parameter.id)
Logger.log( "id: " + id )

try{
// Update the QR spreadsheet
var this_spreadsheet_id = ScriptProperties.getProperty('this_spreadsheet_id')
var ss = SpreadsheetApp.openById(this_spreadsheet_id)
var sheet = ss.getSheetByName("Sheet1")
var range = sheet.getDataRange()
var last_row = range.getLastRow()
var last_column = range.getLastColumn()

for(i = 2; i <= last_row ; i++){
var this_row = sheet.getRange(i, 1 , 1, last_column)
var values = this_row.getValues()[0]
var row_id = parseInt( values[0] )

if ( row_id == id){
var title = values[5]
//var status_txt = values[7]
var range = sheet.getRange(i, 8)
range.setValue("CHECKED OUT")
Logger.log( "Spreadsheet: CHECKED OUT")
break
}
}

var app = UiApp.getActiveApplication()

//Update infoBox
var infoBox = app.getElementById("infoBox")
infoBox.setVisible( true ).setText( title + " has been checked out")

var status = app.getElementById("status")
var status_css = {'background': 'red', 'color': 'white'}
status.setText("CHECKED OUT").setStyleAttributes(status_css)

var grid = app.getElementById("grid")

grid.setStyleAttribute(3, 1, 'background', 'red')

grid.setStyleAttribute(5, 1, 'visibility', 'hidden')
grid.setStyleAttribute(4, 1, 'visibility', 'inherit')


}catch(e){
Logger.log(e)
}

return app

}


… I used CSS to show and hide the non-relevant button. It's a bit clunky, but it does the trick ( please make better code available if you know how, .setVisible(false) didn't seem to work ).

And To Finish...

I added a listing application, if somebody might accidentally view the app without using one of the unique URLs in the spreadsheet. It looks like this. It displays all the cameras and might need some pagination if there were too many to display easily.



Generating a Google Document To Print Off the QR Codes

It was easy to take the QR codes and make a Google Document to easily format and print off onto labels. Like this...
function make_a_document(){
var this_spreadsheet_id = ScriptProperties.getProperty('this_spreadsheet_id')
var ss = SpreadsheetApp.openById(this_spreadsheet_id)
var sheet = ss.getSheetByName("Sheet1")
var range = sheet.getDataRange()
var last_row = range.getLastRow()
var last_column = range.getLastColumn()

var doc = DocumentApp.create("QR Example Printable Document")
var body = doc.getBody()
//var table = body.appendTable()

for(i = 2; i <= last_row ; i++){
var this_row = sheet.getRange(i,1 , 1, last_column)
var values = this_row.getValues()[0]

var id = parseInt( values[0] )
var title = values[5]
var image_url = values[4]
var qr_code = values[3]

// Get the images of the cameras and the QR codes
var response = UrlFetchApp.fetch(qr_code)
var image_blob = response.getBlob()

var response = UrlFetchApp.fetch(image_url)
var qr_blob = response.getBlob()

var paragraph = body.appendParagraph('')
var text = paragraph.appendText(id)
text.setFontSize(24)

var image_paragraph = paragraph.appendInlineImage(image_blob)
image_paragraph.setHeight(150).setWidth( 150 )

var qr_paragraph = paragraph.appendInlineImage(qr_blob)
qr_paragraph.setHeight(150).setWidth( 150 )

var paragraph = body.appendParagraph(title)
paragraph.setFontSize(12)

body.appendHorizontalRule()


}
}


… The Google document with QR codes in looks like this.


… and the application on my iPhone looks like this...




Using Spreadsheets INSTEAD of Forms

Google Forms are a great way to quickly collect data into a spreadsheet but what if the data you are collecting is a bit too complex for a simple form to handle?

We've been experimenting with using a Google Spreadsheet, instead of a Form to gather information and finding that this approach has many advantages. We still use a Form to "initiate" the process, and the data gathered from the form is saved in a "central" spreadsheet.

When the form is submitted, the central spreadsheet makes a copy of a "template" spreadsheet. This spreadsheet is more "human readable" than a regular spreadsheet. When the form is submitted, the data is saved as normal, but it also fills in certain values in the copied template sheet, sets the right permissions and mails all the people who need to know about it a link to edit the template copy.

This template sheet has a "form-like" layout, including help ( shown at the right hand side ) and additional tools added with Apps Script. Tools include the ability to add a link to another Google Drive file. There are times when the data you want already exists ( as a document ) and you don't really want people to copy and paste that data into the form.

The copied template looks like this. Each of the fields has been sized ready to fit the data we expect. Some are even "colour bordered" to help different departments find their bits.

The "Central" Spreadsheet

The central spreadsheet, or the one that receives the input from the initial Form keeps a track of which files have been created, saving links to them in each row. It also has a "status" column that the copied spreadsheets know how to update as they get completed. This makes it really easy to add a simple "Status listing" web app to a Google Site ( shown below with dummy data ).

This means you can give nice "live" summaries to certain groups of people without scaring them with the prospect of looking a huge and hairy spreadsheet.

When Might I Use A Spreadsheet Instead of a Google Form?

If Your Data Will Be Very Textual and Complex

If you want to gather lots and lots of long bits of text, a Google Form might not be the best way for people to enter that data, mainly because a Form expects you add your data in one sitting. ( You can allow people to be able to edit their responses, but somehow this still doesn't feel right ).

We've found that when data is long and textual, even viewing it in a standard spreadsheet if difficult, and so have created lots of scripts to render someone's form data into a Google Document in a more readable format.

If Your Data Needs To Be Filled In Collaboratively

It's much easier to work on a shared spreadsheet that looks like a form than in a per-row spreadsheet.
Additional benefits of using spreadsheets, as opposed to Forms, include:

  • all the changes are saved in the file's "Revision History". You can see who added or deleted what and when.
  • All the edits are saved on-the-fly.
  • You can easily lock down parts of the spreadsheet so that people don't accidentally change it.

The "Insert Comment" feature, with the ability to "+" add someone is fantastic. If a part of the input isn't clear to you, you can ask for help from someone and they get a link to come and comment on that part of the document.

If Your Data Is Modular

In our spreadsheet, we even have a template sheet that, using custom made menus can be used to make "more sheets like this one". This is great in those circumstances when you might want people to submit something like recipes, each with the same items ( such as ingredients, method and picture ) and you want to be able to allow them to add one, two or twenty recipes.

Conclusions

So far, we haven't used this in anger yet, but as an approach I really like it. It uses a combination of Google Apps and doesn't get too fancy. There is a huge temptation to make this sort of system do things that maybe don't need doing.

Keeping it simple has been the main design ethic.

Most of the things we thought we'd need in terms of functionalities have turned out to already exist ( such as "Named and Protected Ranges" ) in the tools themselves, OR they can be achieved by just agreeing to work a certain way ( no software creation required ).

One of the things I like about this approach is that at the end of the day, they are all just first class Google files that one could, if need be, keep updated by hand, but it makes the whole process easier by gently easing all the things you'd need to do by hand, sending emails, adding permissions, creating files, adding calendar entries etc.

I'll let you know how we get on.

Friday, May 24, 2013

Google Apps, New Possibilities for Old Tools?

Last week Google announced a number of new Apps Script features that have been added to Google Documents and Forms and Spreadsheets.

The features themselves may not seem worth shouting that loudly about, but the ability to add Sidebars to documents and add menus and arbitrary user interface items that run Apps Script code means you can start to dream about how you could extend and combine these really powerful objects in new ways.

Google Apps was already a collection of powerful objects ( Documents, Spreadsheets, Drive Files, Forms, Calendars, Sites ) that could be easily combined with Apps Script to create really useful applications, but with the arrival of these new features, the ability to combine them can be more elegant. And because you can create tools and interfaces within the documents you can extend the tools - rather than just combine them.

We've already seen a demo of Bibsto, an Apps Script Bibliography manager that changes Google Documents into Research papers - with added tools for adding references and citations.

We are going to see a flourishing of new custom-made add ons to Google Apps, mark my words. For example, Martin Hawksey has already been trying to create a "Document Map" feature, ala Word. There's a feature I'd love... and if only Martin and I want it, so be it.

More importantly, there's a high chance you might create some innovative tool yourself that makes Google Documents work the way you want them to. We'll see add ons for writers or for educators or for marketers or cheese makers or just you.

Ooooh, and it makes me wonder...

One of my long-standing criticisms of Google, from a UX perspective, is that many of their innovations seem isolated from the other ones - meaning simple features get implemented in one product, but not another creating an overall usability glitches of nothing working quite as you'd expect. For example, how search works, drag & drop, document ownership models, commenting, API access etc are just some of the things that work one way in one product and work differently in another.

The future is already here in Google Apps land, it's just not evenly distributed.

And so, whilst it's great that Google are starting to make scripting features available across two or three products, it does make you notice where the gaps are.

Google, being Google, often fail to see what they're sitting on and cock things up. For example, Google Wave should not have been a product, it should have been the real-time commenting system for all Google products. If delivered well, you would have barely noticed its existence ( except maybe you'd notice how appalling other systems were in this regard ).

So, What Should Google Do?

Mind The Gaps

There is something to the "small pieces loosely joined" idea that has legs. Originally it applied, I think, to blogs and RSS and other web2.0 tools that you could "wire together" to create newer, bigger, more complex things. It also applies to the ideas behind unix and maybe Galls Law ( "A complex system that works is invariably found to have evolved from a simple system that worked" ). Google seem to be creating a compelling landscape of tools wired together using Apps Script.

Google simply need to look at what they have and ask "What if the good stuff was everywhere?".

So, where are Presentations and Drawings apps in the "small pieces loosely joined" mix? They're noticeably missing. Now I imagine most people don't really use Google Presentations and Drawings much. I see most people still using Powerpoint ( uploading .ppxs into Google Drive ) and traditional desktop based graphics tools.

Except, what if you could add scripts to shapes in Google Presentations? Scripts that took the user to the next slide, for example, or went to a slide based on which button you clicked. You'd be able to create a narrative experience, or a quiz or a mini learning object that branched in all sorts of directions. Kids could use it.

And what if, like you can in Google Sites, you could insert videos and documents, or get data via Apps Script and put it into fields on screen? You would have an interface builder anyone could use. It would be like an online HyperCard - a tool with which people could pull various resources together and make them work the way they want ( without serious programming ).

If you could script the Drawing app you might be able to create simple animations, or maybe simple visualisations.

And the thing is, all that functionality is sort of sitting there already. It doesn't need much in the way of design, it just needs someone to connect the dots.

And no, Google Presentations or Drawings might not be the *best* tool for creating presentations in, but if they were scriptable, like Documents, they'd suddenly become a new thing, loaded with possibilities, rather than an old thing hanging about being slightly embarrassing.

I think this process of making sure that your innovations touch all parts of your product range is a cheap one to conceive and implement - it's dealing with lots of known knowns.

There are dozens of "Mind The Gap" innovations Google could make that I can guarantee would initiate a huge wave of creativity using Google Tools. How do I know? Well, people are just like that.












Thursday, May 16, 2013

Document Sidebar and Menus in Google Docs

For a long time I've been saying that Google need to make sure that their left hand knows what the right is doing. There are pockets of innovation in some products that are notably missing from other products. And whilst I don't advocate insisting that each department should consult with each other department before a beautiful carbuncle can be brought into the world, it's nice when you feel that different Google teams are at least on speaking terms.

One example ( and there are dozens and dozens ) of innovation insularity is the features that are in a Google Spreadsheet. With a little code and ingenuity you can add menus, and pop ups and whole new interfaces to a spreadsheet, but this can't be done in a Google Document. "Why not?" you might ask. And rightly so.

So, I was very pleased when Google announced at Google I/O ( their big developers conference happening right now ) that the features from Spreadsheets were being added to Google Documents. Take a look at this document. It has custom menus and a custom sidebar.


Imagine...

… what you could use those menus for. Imagine what content, or links, or even more clever stuff you might have in the sidebar. It could be pre-created content, by you the lecturer or it could be content that is derived from whatever you are typing. The menus might remind you when the essay deadline was, or email the document to random peers. I've already made some code to save a Google Document to Blogger that saves the images into a publicly hosted GDrive folder. This article was written in Google Docs, not Blogger which is a bit shonky nowadays. Imagine how easy it will be to add a "Save to Blogger" menu item. This is exciting stuff.


Except hold your excitable horses.

The only problem is, that the code to do the things you can do in a Google Spreadsheet is completely different to the code you need to do to do it in a Google Document. The code below shows how to add a menu and a sidebar to your Google Document ( go to the Tools >> Script Editor menu and paste it in, then run onOpen. You will need to authorize it ).

function onOpen() {

// Display a sidebar with custom UiApp content.

var uiInstance = UiApp.createApplication()
.setTitle('My UiApp Sidebar')
.setWidth(250);
uiInstance.add(uiInstance.createLabel('This side bar can contain content that is pre-defined by the lecturer.'));
uiInstance.add(uiInstance.createLabel("It might contain some regular links to useful stuff.

"))
uiInstance.add(uiInstance.createAnchor('It might contain a link to plagiarism', 'http://www.york.ac.uk/integrity/plagiarism.html'))
uiInstance.add(uiInstance.createLabel("The really interesting part, for me, is that it might contain dynamically generated links to stuff of relevence to what you are writing. "))
uiInstance.add(uiInstance.createLabel("What if a document had links to required reading, or a link to your mentor?"))
uiInstance.add( uiInstance.createImage('https://si0.twimg.com/profile_images/3457614642/d6b665b4c213fe02ec28dc3e94db6e0b.jpeg').setHeight(100).setWidth(100))

DocumentApp.getUi().showSidebar(uiInstance);
DocumentApp.getUi()


.createMenu('My Menu')
.addItem('Do something...', 'do_something')
.addSeparator()
.addSubMenu(DocumentApp.getUi().createMenu('My Submenu')
.addItem('One Submenu Item', 'do_something_else')
.addItem('Another Submenu Item', 'do_nothing'))
.addToUi();


function do_something() {
DocumentApp.getUi().alert("This could do something")

}

function do_something_else() {
DocumentApp.getUi().alert("This does something too. It could go and get live weather data and insert it into the document. Ha ha!")
/* THIS DOESN'T WORK
Browser.msgBox("This does something too. It could go and get live weather data and insert it into the document. Ha ha!")
*/
}

function do_nothing() {

DocumentApp.getUi().alert("This does something. It could be anything. It might mail this doc to someone, or add it as a reference to a spreadsheet.")


}



I tried hacking the code into a Spreadsheet. It did sort of work. The sidebar appears as a popup dialog, but the menus don't work at all.



Whilst initially really encouraged that Google were "pulling the strands together" at last and making cool behaviours consistent across their product range, I am a little disappointed that it isn't completely idiot proof ( I have a stakeholder interest in things being idiot proof ).

My guess is that this is a sign of things to come, that the process of innovation consistency is just starting and one day I will be able to add Apps Script to my Drawings and Presentations and whatever else from Google.

 

© 2013 Klick Dev. All rights resevered.

Back To Top