Tutorial: Creating a Dynamic Printable Workbook

[Edit: Here is an updated tutorial]

Here is a Dynamic Workbook that I built with the help of an experienced javascript developer. It allows for a custom workbook to be assembled based the choices a user makes, which will help them take their notes and other relevant information and references away from the course.

There is a bit of work involved to get this set up properly, but it’s not too tricky. Promise.

Here is a demo of the functionality.

Here is a .zip that contains the files you’ll need.

I’ll start by outlining the basic steps for those who just want a quick overview and then I’ll provide a more detailed description.

Here are the basic steps to make it work:

  1. Figure out how many of the users' responses you want to track and create a variable to store each response. These variables should be titled Q1, Q2, Q3, Q4 and so on. 
  2. Set up the printUserTitle and printUserName variables. These control the title and name shown at the top of the workbook.
  3. Modify the print.html file to include your desired content. Note that the users' responses will be inserted wherever this exists:
    <div id="answer1" class="answer"></div>
  4. Create a ‘Workbook’ button in your course using the following javascript: window.open("print.html","_blank","width=800,height=600,menubar=no,scrollbars=1");
  5. Add the jquery folder and the print.html, process.js and style.css files to your published output.
  6. If you are using a LMS, see here
  7. Upload and test.

-----

Here is a more detailed description.

It is best if you build the bulk of your course before you add this functionality. Although when building the course you’ll want to keep the following in mind:

  • Any responses that you want sent to the workbook should be stored in a text variable that starts with a capital Q followed by a number. For example, Q1, Q2, Q3, Q4 and so on. The variables must be named this way so that they can be found by the workbook. (If you don’t know what variables are, I suggest you read this article before continuing.)

Once your course is built, you’ll want to add the following to your Storyline file:

  • Create a text variable called printUserTitle. Its value will determine the title that is displayed in the workbook. 
  • Create a text variable called printUserName. Its value will determine the name that is displayed in the workbook. 
  • Create a text entry field at the start of your course where the user can enter their name and set it to adjust the value of the printUserName variable.
  • Create a button in your course that will be used to open the workbook. It should execute the following javascript when clicked:
    window.open("print.html","_blank","width=800,height=600,menubar=no,scrollbars=1");

Then you can publish your course as per normal (although you don’t want to zip the files just yet).

-----

The next step is to edit the print.html file.

You’ll need a text editor to do this, such as Notepad.

When you open the file, you’ll be greeted with the following beautiful sight:

<!DOCTYPE html>
<html>
<head>
<title>Workbook</title>
<script src="jquery/jquery-1.11.0.min.js"></script>
<script src="process.js"></script>
<link rel="stylesheet" href="style.css" type="text/css" />
<style media="print">
#printDiv {display:none;}
</style>
</head>
<div id="container">
<div id="printDiv"><button id="print"></button></div>
<div id="staticContent">Name: <span id="userName">.....</span></div><br/>
<div id="staticContent">Date: <span id="date">.....</span></div>
<div>
<h1 id="title">.....</h1>
</div>
<div id="staticContent">This is static content that will be the same for everyone. You can provide as much of this as you need in the workbook. Did you know that a group of toads is called a knot? Apparently you love the below more than a knot of toads.
</div>
<div id="answer1" class="answer"></div>

<div id="staticContent">If you live in Virginia and have a bathtub outside your house, you are currently breaking the law. Now, your thoughts on pickles.
</div>
<div id="answer2" class="answer"></div>

<div id="staticContent">At this exact moment in time, there are at least 1,800 thunderstorms in progress over the earth's atmosphere. Anyway, back to pickles.
</div>
<div id="answer3" class="answer"></div>

<div id="staticContent"> Did you know that most lipstick contains fish scales? Well, herring scales to be precise. You were asked not to type anything for one of the questions so you can see what happens.</div>
<div id="answer4" class="answer"></div>

<div id="staticContent">Did you know that dolphins sleep with one eye open and that jellyfish are 95% water? Or was that the other way around? Wait ... do jellyfish even have eyes?</div>
<div id="answer5" class="answer"></div>

<div id="staticContent">Please remember that while it is possible to lead a cow upstairs, it will not be possible to lead a cow downstairs in the future. This could create a problem. Are pickles sounding interesting yet?</div>
<div id="answer6" class="answer"></div>

<div id="staticContent">100% of lottery winners gain weight. So there's that. In other news. </div>
<div id="answer7" class="answer"></div>

</div>
</body>
</html>

You’ll need to change some of this, but it isn’t as scary as it seems. You can start by ignoring all of this stuff at the top (it’s just telling the browser what’s up and displaying the info at the top of the workbook):

<!DOCTYPE html>
<html>
<head>
<title>Workbook</title>
<script src="jquery/jquery-1.11.0.min.js"></script>
<script src="process.js"></script>
<link rel="stylesheet" href="style.css" type="text/css" />
<style media="print">
#printDiv {display:none;}
</style>
</head>
<div id="container">
<div id="printDiv"><button id="print"></button></div>
<div id="staticContent">Name: <span id="userName">.....</span></div><br/>
<div id="staticContent">Date: <span id="date">.....</span></div>
<div>
<h1 id="title">.....</h1>
</div>

You’ll want to start making changes where it says:

<div id="staticContent">This is static content [etc.]

If you want text displayed at the top of the workbook, this is where you enter it. Simply put the text you want shown between these two things:

<div id="staticContent">
</div>

So it looks like this:

<div id="staticContent">This is exactly where your beautiful words should be.
</div>

Then you’ll notice the next line down in the print.html file, says:

  <div id="answer1" class="answer"></div>

This is where the response that is stored in variable Q1 will be displayed. You don’t need to make any changes to this.

The part below has some more static content:

<div id="staticContent"> If you live in Virginia and have a bathtub outside your house, you are currently breaking the law. Now, your thoughts on pickles. 
</div>

And then there is some code that will result in variable Q2 being displayed:

  <div id="answer2" class="answer"></div>

This pattern continues throughout the print.html file. Essentially, static content should be wrapped in:

<div id="staticContent">
</div>

And responses from Storyline Q# variables will be shown wherever these exist:

  <div id="answer#" class="answer"></div>

(Note that you’ll need to swap that # with a number in the code above).

Make sense? Cool. Save your print.html file and let’s move on.

-----

Okay, next up we need to check the date. I’ve set it to display properly, but I’ve heard that some of you do weird things like have the month before the day… So if 30/10/14 looks weird to you then you’ll want to open process.js in your text editor and change this:

  $("#date").html(curr_date + "-" + curr_month + "-" + curr_year);

To this:

  $("#date").html(curr_month + "-" + curr_date + "-" + curr_year);

Or if that doesn't work, maybe even this:

  $("#date").html("Dance Like" + "-" + "It's" + "-" + "1999");

Actually, ignore the last example, that's just silly.

If you are publishing to a LMS, you'll also need to replace:

var player = opener?opener.top.GetPlayer():{} ; 

with

var player = opener?opener.GetPlayer():{} ;

Save your process.js file. 

Now, if you are comfortable with CSS, you can change the look of the workbook by editing the style.css file. 

I won’t go into detail here, but if all you want to do is change the green text colour to something else, open style.css in your text editor and change the last line from:

.answer p {color:green;} 

to 

.answer p {color:red;}

Right, so now it’s just a matter of copying the jquery folder, and the print.html, process.js and style.css files to the same folder that contains your published output.

This needs to run from the web, so if you try and test it locally, it won't work. Upload it to your LMS or server or secret lair or whatever and you should be good to go.

As far as known issues go:

  • This won’t work Articulate Mobile Player (as it's allergic to javascript) 

I hope that this is helpful. 

Edit 11/10/18: If you'd prefer a PDF option, see here.

81 Replies
Andy Turner

Hi - I'm just posting this as a request for anyone looking to implement one of these solutions (I'm new - hopefully this is allowed). Pleas contact me if you are interested - we're looking to start asap:

We have a developed Articulate Storyline course that is a questionnaire (20 questions) each with specific feedback. The Storyline design is done but we need to export the data at the end of the process to create a PDF or any form of printable file.

User:
Answers 20 questions each with 2-5 options selected via a radio button
We then want them to have a button to print the results or save them

The format of the outout would be:

Page 1:
Name
Date
Score

Pages 2 on:
Question (1-20)
Response (specific feedback based on the selected answer)
Score/category (low, medium or high)
Why we asked this (text specific to the question - always stays the same)

The work has been done in Articulate Storyline 2 - all we need an expert to take this data and produce the file. I can send examples that are very specific. Please note that this must be part of the final SCORM files for the client and not have any server side java etc...

Thanks -

Designboy Chris

Okay, tested it, firstly I loved reading the instructions they made my day happier. I love that it works so well to what I was after! Can now sit for an hour and rest today without having to code stuff I have no idea to do!

One question... 

Can you automatically get the results to email off to the host website owner from this system? I want to collect the data too and this would actually make my whole life SORTED!!!

Matthew Bibby

Glad it worked well for you Chris and that you enjoyed reading the instructions (these things should be fun!).

There isn't a reliable way to get the results to email off to the host through this system. It's also not something I'd recommend exploring unless you know the devices and email clients that all of your users have installed.  

Instead, I'd look at passing the results to a google spreadsheet. For more info on how to do this see this post (make sure you read the whole thread as there are a couple of corrections to the original code). You may also find the explanation I provided here helpful. 

George Champlin

I've created a native version of the process.js script so you don't need jQuery. This should work in all browsers including IE8+.

Here's the code

//This will get the current date for the user
var d = new Date(),curr_date = d.getDate(),curr_month = d.getMonth() + 1,curr_year = d.getFullYear();
var player = opener? opener.top.GetPlayer():{} ;
///LMS player = opener?opener.GetPlayer():{}
/* ------------------ this is ONLY for debugging ----------- */
if (!player.GetVar) player={
GetVar : function(idx) {
return {printTitle:"Please access this workbook from within the module", printUserName:"Unknown"}[idx]
}
}
/* ------------------ the above is ONLY for debugging ----------- */
function getOne(sel) {
return document.querySelector(sel);
};
function getAll(sel) {
return document.querySelectorAll(sel);
};
var ready = function() {
if (window.print) {
getOne("#print").onclick = function() { window.focus(); window.print() };
getOne("#print").innerHTML = "Print";
getOne("#printDiv").style.display = "block";
}
var title = player.GetVar("printTitle"), userName = player.GetVar("printUserName");
getOne("#title").innerHTML = title;
getOne("#userName").innerHTML = userName;
getOne("#date").innerHTML = curr_month + "-" + curr_date + "-" + curr_year;
var numberOfAnswers = getAll(".answer").length;
for (var i=1;i<=numberOfAnswers;i++) {
var $content = getOne("#answer"+i);
var q = player.GetVar("Q"+i);
q=q?q.replace(/\n|\r/g,"<br/>"):"No response provided."
// window.console && console.log(q);
$content.innerHTML = "<p>"+(q)+"</p>";
}
};
document.onreadystatechange = function() {
if(document.readyState === "interactive") {
ready();
}
}
George Champlin

Matthew,

The line feeds were not getting replaced until I added something to the replace pattern. 

This is the new pattern:
q.replace(/\n|\r/g,"<br/>")

It will replace a line feed (\n) or a carriage return (\r) with a "<br />" tag.

I've updated my native code above as well as the attached js file.

felisha johnson

Hey Matthew,

Thanks for this...great job.  I noticed that the questions in your demo were all single answer (true or false). Has anyone create a version with multiple choice questions?  I'm just getting started on a project and am thinking through the programming required to handle those types of questions.

felisha johnson

Hey Matthew,

This may be a silly question, but here it goes...

In your demo, you use Q1, Q2, Q3, etc. for your variables to store each response. If I wanted to rename the variable to coincide with my module content I could use something else [for example: WS1, WS2, WS3, etc.].

However, I would have to update the javascript file [for example: var q = player.GetVar("WS"+i);] to reflect this. Is that correct?

Again, it may be a silly question but I'm just trying to follow the logic and ensure that I don't mess up anything.

Matthew Bibby

Hi Felisha,

This should work fine with multiple choice questions. Just make sure they store the response in one of the Q1, Q2, Q3, variables. 

Regarding changing the javascript file to work with different variable names, I'm not sure off the top of my head (it was a while ago when I last looked at this). Give it a try, see if it works, if not, try fiddling with something else! 

Daniel Bolia

Hi Matthew,

A while back I created a note function based on your technique here and a blog from eLearning Brothers. Although I noticed that the character limit for the answer text box was 256, I was able to implement the note function in one of my courses.

On revisiting your this post I noticed the process-2.js file posted by George Champlin. I tested this method with the new js file and it worked as designed. It's too bad that there is a character limit for the data entry fields.

I'd like to implement Steve Flowers' method of carrying the supporting files (print.html, *.css, and *.js) inside a webobjects folder. Doing this will eliminate the need to move files after publishing. I know how to adjust the html codes to point to the new css and js file locations. However, I'm not sure how to change the initial workbook javascript   window.open("print.html","_blank","width=800,height=600,menubar=no,scrollbars=1"); to point to a new print.html location. This new location would be ...story_contents/webobjects/6MR9VTradkY/print.html. The 6MR9VTradkY folder name would be different from the example show here.

Another reason I have for revisiting your dynamic workbook technique was to examine the javascript again. I was hoping to find a solution for a problem I recently discovered. The problem has to do with playing a published course, which has the Player.GetVar function in the java script, on IE in a citrix environment. It doesn't work. For example your dynamic workbook will only display static contents and will return "unknown" for the printUserName field. All other dynamic fields have a returned value of "No response provided". The date field works because it's not relying on the player.GetVar function. Please see attached picture. I should stress that when played on chrome in the same citrix environment, everything works just great.

I thought that the var player = opener? opener.top.GetPlayer and subsequent player.GetVar() codes, placed in a separate JS file might work, but unfortunately it returned the same result as when the GetPlayer functions are called in a SL trigger.

Additional details on the player.GetVar() problem is posted at https://community.articulate.com/discussions/articulate-storyline/getplayer-script-does-not-work-in-citrix-environment#reply-361484

Thank you,

Matthew Bibby

Hi Daniel,

I think you just need to update that code to point to the right location. So rather than window.open("print.html" [etc.]  you'd change that to something like window.open("story_contents/WebObjects/6MR9VTradkY/print.html"(note, I haven't tested this, so I could be wrong, this is just off the top of my head).

Regarding your question about the character limit, I'm not 100% sure. I don't recall that being an issue when this was originally built, but I don't have time to investigate at the moment. I'll add it to the list of things to look into when I have a chance.

Regarding your Citrix issue. It's probably something to do with the security settings. When I used to use Citrix years ago I ran into similar (weird) issues. Does your project work okay in IE outside of Citrix?

The var player = opener? opener.top.GetPlayer may need to be changed depending on how the project is opened (as noted in the original post var player = opener?opener.GetPlayer():{} ; may be more appropriate if you are running this from within an LMS.

Sorry I can't be of more assistance...

Abhishek Mukherjee

Thanks for the wonderful tutorial on Dynamic Workbook.

I just want to add another feature.

I have 6 courses which will contain link to the same (single) workbook divided into 6 sections. Each of the section will contain 5 or 6 questions which will be populated with user answers.

Is there any way so that when one section is filled up (lets say 1st course), the answers will be there from 1st course when the user attempts the 2nd course and so on ?

I need to permanently save the answers in the workbook when user enters the answers.

I have created two sample course (links below) containing few questions. They have link to the same  workbook (which is hosted on same web server in a folder).

http://bit.ly/29XIY0C

http://bit.ly/29XJuLT


Any help will be appreciated.

Matthew Bibby

Hi Abhishek,

I'm glad you liked the tutorial. 

I'm not sure how to go about adding that feature. Communicating between courses in that way can be tricky, especially if you are using an LMS. 

It may be possible to use cookies to store the responses on the users machine, but I suspect this approach may be fraught with issues. Another option would be to pass the responses to a database and then bring them back at the beginning of one of the courses, but this would quickly get complex. But if you do go down that path, this tutorial from Zsolt on communicating with a web server might help.

Hopefully someone else here has some other ideas on how to achieve this.

Abhishek Mukherjee

Can it be setup in this way ?

1.) user types the answer which is sent to Google Sheet or Form.

2.) when user click the workbook link, i.e. print.html , the data (answers) are pulled from Google Sheet instead of Storyline.

In this way the answers can be permanently stored in Google Shhet and can be fetched fom there whenever a user wants.

Matthew Bibby

Abhishek, that may be possible, but I'm not 100% sure. 

Theoretically, when each module loads you could grab the variable values from the Google Sheet and pass them to Storyline so that they could be used by the workbook. 

The tricky part would be determining which records belong to which user. How would you know which answers to grab? Would it be based on the learner's name? What if someone else logged in with their name and changed all of their answers? And I suspect there would be other issues as well...

I'm not sure. I suspect you may be better to look into other solutions.

Matthew Bibby

Hey Zdravko,

I'm glad you like it! The beauty of being able to use JavaScript in Storyline is that we can create things like this ourselves with a lot more flexibility than an inbuilt option would likely provide. That being said, you can submit a feature request to Articulate here letting them know you'd like to see something like this included.