Forum Discussion
Articulate Storyline: Export to Google Drive
On the Articulate user days in Utrecht (2015), we held a session about exporting Articulate Storyline variables to Google Drive (Spreadsheet). This export is achieved via JavaScript (jQuery).
Use the button Source to download the Storyline project. If you want the same lay-out as the example above, please install the following fonts (see source file):
- flaticons-stroke.ttf
- fontawesome-webfont.ttf
Why do you want to make an export to Google Drive?
- No LMS (or database) available;
- Store more information than just the test results. You can export all Storyline variables to Google Drive.
Why using Javascript?
Besides using Javascript it's also possible to embed a Google form as WEB object. This is a lot easier than using Javascript.
We have chosen for Javascript because we would like the ability to change the appearance of the form. In addition, we want to collect data across multiple slides and then store them in Google Drive.
UPDATE 2017-06-06:
There is also an article available for importing records of a Google Spreadsheet into Storyline:
Articulate Storyline: Import from Google Drive
UPDATE 18-10-2018: PROBLEMS WITH NEW SPREADSHEET, TRY TO USE A TEMPLATE
Google changed somthing within the mechanism of storing variables into a Google Spreadsheet. Creating a new spreadsheet can give you problems for storing variables. Before you try the steps below, please use a template:
Go to the page below and use the button Use this template:
https://drive.google.com/previewtemplate?id=1vrqb9ykwCSxzjcBIpYO7P9AXPyh37J3HviSwhZvYc_U&mode=public
And go further with step 4 below!
Export to Google Drive
The export consists of several steps. Some steps take place on the side of Google. For these steps you will need a Google account. The remaining steps take place in Articulate Storyline.
Below you'll find the steps to create the export to Google Drive:
1. Login with your Google account
Go to the page below and login with your credentials:
https://docs.google.com/spreadsheets/
2. Create a new spreadsheet
Click on the Plus-sign (+) to create a new spreadsheet.
3. Rename the sheet to DATA
Give the spreadsheet a title and change the name of the sheet to DATA.
4. Add extra columns
Add extra columns you would like to use. Probably these columns will have the same name as the variables in Articulate Storyline. As example: the column names name, email and message like in the source project.
You can add the column date in the spreadsheet, if you would like to save the date when the form is sent to Google.
The column names needs to be identical to the variable names in Articulate Storyline. The column names are case sensitive.
5. Copy the ID of your form
Find out your spreadsheet ‘key’ by looking in the address bar, the key is the long series of letters and numbers after /d/ and before /edit: Like:
https://docs.google.com/spreadsheets/d/1AzBuim89ma_ght1-O14cksVzXrQL5Vh4XnRqY9OM_gc/edit#gid=0
The KEY will be = 1AzBuim89ma_ght1-O14cksVzXrQL5Vh4XnRqY9OM_gc
Save this KEY in a Notepad file to keep safe, or other application, you will need this ID in step 8.
6. Open the Script Editor
Open the script editor Tools, Script Editor.
7. Paste custom script
If you are using the template, then you can skip this step. The code is already in the template available. If not, paste the script below, which is needed for importing the Storyline variables into this spreadsheet:
// 1. Enter sheet name where data is to be written below
var SHEET_NAME = "DATA";
// 2. Enter the KEY of your form
var KEY = "KEY"
// 3. Run > setup
// 4. Publish > Deploy as web app
// - enter Project Version name and click 'Save New Version'
// - set security level and enable service (most likely execute as 'me' and access 'anyone, even anonymously)
// 5. Copy the 'Current web app URL' and post this in your form/script action
// 6. Insert column names on your destination sheet matching the parameter names of the data you are passing in (exactly matching case)
var SCRIPT_PROP = PropertiesService.getScriptProperties(); // new property service
// If you don't want to expose either GET or POST methods you can comment out the appropriate function
function doGet(e){
return handleResponse(e);
}
function doPost(e){
return handleResponse(e);
}
function handleResponse(e) {
// shortly after my original solution Google announced the LockService[1]
// this prevents concurrent access overwritting data
// [1] http://googleappsdeveloper.blogspot.co.uk/2011/10/concurrency-and-google-apps-script.html
// we want a public lock, one that locks for all invocations
var lock = LockService.getPublicLock();
lock.waitLock(30000); // wait 30 seconds before conceding defeat.
try {
// next set where we write the data - you could write to multiple/alternate destinations
var doc = SpreadsheetApp.openById(SCRIPT_PROP.getProperty(KEY));
var sheet = doc.getSheetByName(SHEET_NAME);
// we'll assume header is in row 1 but you can override with header_row in GET/POST data
var headRow = e.parameter.header_row || 1;
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var nextRow = sheet.getLastRow()+1; // get next row
var row = [];
// loop through the header columns
for (i in headers){
if (headers[i] == "Timestamp"){ // special case if you include a 'Timestamp' column
row.push(new Date());
} else { // else use header name to get data
row.push(e.parameter[headers[i]]);
}
}
// more efficient to set values as [][] array than individually
sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);
// return json success results
return ContentService
.createTextOutput(JSON.stringify({"result":"success", "row": nextRow}))
.setMimeType(ContentService.MimeType.JSON);
} catch(e){
// if error return this
return ContentService
.createTextOutput(JSON.stringify({"result":"error", "error": e})) .setMimeType(ContentService.MimeType.JSON);
} finally { //release lock
lock.releaseLock();
}
}
function setup() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
SCRIPT_PROP.setProperty(KEY, doc.getId());
}
8. Paste your key
There is one place in the script where it says var KEY = "KEY". Copy and paste your key between the "".
9. Run the script
Run the script via Run, Run function, setup. The first time you run the script it will ask for permission to run. You will need to grant it. If you run the script for a second time you won't get any popup. This is an indication it has run successfully.
10. Deploy a web app
Go to Publish, Deploy as web app. Enter a project name (optional) and set security level. Choose for Me and access Anyone, even anonymously. Click on the button Deploy to create the web app.
11. Copy the Current web app URL
Copy the 'Current web app URL' and paste it in a Notepad file to keep safe.
Example URL:
https://script.google.com/macros/s/AKfycbyoP8c3_wlons5PSHx1W8PWJx4pn7t3ch-_IxTz0dVIKFw1AGLN/exec
12. Add jQuery library
In Articulate, add a trigger to run javascript (Execute Javascript) and use the code below.
This code will add the jQuery library to this project, so you won't have to change the HTML files after publishing the project. The jQuery library is needed for exporting the information to Google Drive.
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.src = '//code.jquery.com/jquery-1.11.0.min.js';
script.type = 'text/javascript';
head.appendChild(script)
13. Store information
Add another trigger to run Javascript (Execute Javascript). You can use the code below.
Replace the value Current web app URL for the webapp url you've saved in step 11.
Below the webapp URL, you can place the column names of the spreadsheet and the Storyline variables. Please be aware of the comma if you add multiple variables.
var player = GetPlayer();
//PLACE YOUR WEB APP URL
WEB_APP_URL = "Current web app URL";
// STORE ARTICULATE STORYLINE VARIABLES
// "Columnname_Google_Spreadsheet" : player.GetVar("Name_Storyline_Variable")
// ATTENTION: Use a comma if you use multiple Storyline variables
storyline =
{
"date" : new Date().toJSON().slice(0,10), //STORE DATE
"name" : player.GetVar("name"),
"email" : player.GetVar("email"),
"message" : player.GetVar("message")
}
Don't delete the row below, if you would like to save the date when the form is sent:
"date" : new Date().toJSON().slice(0,10), //STORE DATE
14. Export code to Google Drive
Latest Javascript code. Add another trigger to run Javascript (Execute Javascript). You can use the code below. This trigger will send the information from step 13 to Google Drive.
//DELAY SO JQUERY LIBRARY IS LOADED
setTimeout(function (){
//Export to Google
$.ajax({
url: WEB_APP_URL,
type: "POST",
data : storyline,
success: function(data)
{
console.log(data);
},
error: function(err) {
console.log('Error:', err);
}
});
return false;
}, 1000);
15. Publish to SCORM or WEB format
Publish your articulate project to WEB or SCORM format. You need to host it on a WEB server or somewhere like SCORM cloud (or a LMS).
This export will work in Flash and HTML5 output. You can't use the Articulate Mobile Player, because it won't support Javascript code.
Thank You
Thanks Kate Robertson for making the blog post below:
Exporting Variables into a Google Spreadsheet
I started with your setup and then have tried to simplify the steps by changing the Javascript code and importing the JQuery Library.
- JonEzell-a06257Community Member
Seriously, every time I show someone something I've built in Storyline: "Oh, wow, that's cool! And we can get it to send the data from these interactions back to us?"
Me: "Well, you'd think so .... "
Hi Jon,
Sorry to hear about your dissatisfaction with the data that Storyline 360 sends to an LMS. I'd like to hear more about which data would you like to have added to the reporting, so I can share your feedback with our product team.
First, allow me to share with you some articles which contain information on the kind of data that gets sent to an LMS from Storyline 360:
Additionally, you can also send custom variable values using different methods:
I'm sure you're expecting to see information that is not included in the data covered in the articles, as well as provide support for receiving data without the aide of an actual LMS to capture quiz data - would you mind sharing what robust data is for you in terms of information that is captured from a quiz and sent back to the user? Hearing your feedback and everyone else's from the community will definitely help to paint a picture of how the application can be improved.
Looking forward to hearing from you!
- Jürgen_Schoene_Community Member
"All use of the Google Sheets API is available at no additional cost"
https://developers.google.com/sheets/api/limits#pricing
can you post a link for the new price
- doankenCommunity Member
I followed the instruction script but the data still cannot be written to google sheet, below is my Demo file, please help, thank you very much!
- MathNotermans-9Community Member
If you use the console of the browser for testing this... you get this error message...
user.js:55 Uncaught ReferenceError: $ is not defined
at user.js:55:1
This should tell you jQuery is not loaded and/or found.- doankenCommunity Member
Hello Math Notermans, I still don't understand this error, I checked again, and running it still can't write data to the worksheet, please help me, I used the sample worksheet in the tutorial. guide, thank you very much!!!
- MathNotermans-9Community Member
Next step is adding a proper url so jQuery will be loaded.
Changing this on the first JS trigger...script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js';
And jQuery gets loaded...
But we now get another ( expected ) error in the console.
RULE 1: When working with Javascript. Use your browser console to check for errors !!!user.js:56 Uncaught ReferenceError: WEB_APP_URL is not defined
at user.js:56:6
This is a scope error. The WEB_APP_URL is defined in trigger 2, and called in trigger 3. But trigger 3 doesnot have any knowledge of the contents of trigger 2. Thats the way Storyline works. Every trigger has its own scope. Only way to work around that is having all code in 1 trigger... or using a global Javascript or using variables to pass variables along. The last solution im gonna use now.
The same scope mistake is made with the 'storyline' variable in your script. Always keep scope in mind in Storyline ! As the var storyline is JSON based data, im not 100% sure if we can sent that through Storyline variables...that are only strings. We will see... - doankenCommunity Member
Hello, Math Notermans, currently I have a problem, in the spreadsheet, I want to add a number of correct sentences column and a rating column. But the data cannot be saved into formula lines. even though I added variables in storyline. Please help me, I thank you very much!
- MathNotermans-9Community Member
You still have the scope errors in this Storyline. Each trigger has its own scope, so it has no clue of other triggers.
Do read my previous comments, especially on scope. Storyline triggers DONOT have knowledge ( = scope ) of what happens in other triggers. Only way to fix that is either use all in 1 trigger ( as done in the sample i added ) or use Storyline variables to pass them along.
The sample added works as you can see in your sheet.
Kind regards,
Math
- doankenCommunity Member
Hello Math Notermans, if we use the score variable, there are many different players, how can we compare their scores and rank them based on the scores they achieved? Thank you Math Notermans!
- MathNotermans-9Community Member
Reading data back in you do using
$get
or$getJSON
. The problem with these approaches is that you get all data ( as an Object ) and have to parse that.
Examples:$.get(WEB_APP_URL, function(data, status){
console.log("Data: " + data + "\nStatus: " + status);
});
$.getJSON(WEB_APP_URL, function(result){
$.each(result, function(i, field){
console.log(i+" | "+field);
});
});
When you succesfully have all the data. You can put it into arrays and sort those. Then you can show the top 3 or whatever. - MathNotermans-9Community Member
A great solution to reading and getting data from Storyline is Sheetsrock. This is a Javascript library that makes it a snap to not only read Googlesheets data but also sort and query it. Thus you can target any publicly shared Googlesheet, read it and sort it based on score or whatever and then show the top scorers.
2 things to be aware. Your sheet needs to be publicly shared ( yours is :-)) and you need to show your data in a table. Well not perse show it, but parse it in. As the Storyline tables as is are not easily accessible by Javascript ( due to the fact they are SVG and have no regular HTML-names or IDs ) you have to add a HTML-table.
As you can see in the added Storyline, the data is read into the table.
This code on fill Table shows it like this...$("#googleSheetTable").sheetrock({
url:googleSheet
});
To sort the data into some top scorers order you would need to use Sheetsrock query methods.
It is basically Google query code and you can find all info how to sort and query there.
These lines of code limit the amount of entries processed to 10 ( fetchSize: 10 )
and the query makes sure no 'null' entries are showm and orders ascending.$('#googleSheetTable').sheetrock({
url: googleSheet,
query: "select A,B,C,D,E,F where D is not null order by D",
fetchSize: 10
});
Finally this is the code that gives you a descending list out of all entries.$('#googleSheetTable').sheetrock({
url: googleSheet,
query: "select A,B,C,D,E,F where D is not null order by D desc",
});
Only thing missing now, is limit the view in the table to only show the top 3 or top 10.
Luckily Sheetsrock is smart and fetchSize does that properly.$('#googleSheetTable').sheetrock({
url: googleSheet,
query: "select A,B,C,D,E,F where D is not null order by D desc",
fetchSize: 10,
});
Well i guess you can tackle this now too :-)
Kind regards,
Math- doankenCommunity Member
Hello Math Notermans, I don't know much about javascript. Currently, the
data sending issues have been resolved. There's only 1 problem left, the
rating column doesn't work, it always shows 0. I don't know where the error
is, please help Math Notermans to look at my attached file, thank you very
much!
- doankenCommunity Member
I don't understand why there is no attached file. In the Demo file I changed the language, everything worked, except for the rating column, it always showed 0, I don't know what I did wrong. Thanks to Math Notermans for help. Thank you very much!
- MathNotermans-9Community Member
your making the same errors. Scope.
- MathNotermans-9Community Member
And you really need to work on your Storyline skills. Ratings is nowhere set, so offcourse it stays at 0. When trying to take on something like this...first ensure your Storyline works flawless. IT DOESNOT NOW. Then you can learn Javascript to enhance it.
- MathNotermans-9Community Member
But for now i solved it for you. Apparently somehow ( latency, timing ) the last row of the data wasnot sent to GoogleSheets. And the 'ratings' variable was never set to anything else then 0. That being a basic Storyline error. Always doublecheck your variables. And give them proper names. I use 'varnameStr' for string variables and 'varnameNum' for Numeric variables to see what they are in 1 blink.
So i added a random value to the 'ratingsNum' variable and combined scripts to watch for scope.Kind regards,
Math - doankenCommunity Member
Chào Math Notermans, mình xem file bạn gửi thấy gán giá trị số ngẫu nhiên ở cột xếp hạng là không đúng ý mình. Tôi muốn dựa vào cột điểm trên google sheet và xếp hạng người chơi từ cao xuống thấp. Vui lòng xem lại giúp tôi (File Demo). Cảm ơn rất nhiều
- MathNotermans-9Community Member
Im very sorry not gonna fix this for you. Remove the trigger that sets the variable to a random value...and its 0 allover again ;-) Dont try to do that in Storyline. So remove it completely from the writing to Googlesheets. Easiest way is doing some formula directly in Googlesheets calculating the rating.