Need a customized certificate? To-do list? Agenda? Jobaid?

I helped someone earlier this year to create a certificate for download that included the name and date of the user after completing a course. This is a prototype I wanted to share in case you have similar needs.

First, here’s what it can look like:

You can play with the setting and TRY IT OUT below. “astrologistics” is not a word but I didn’t want to hurt anybody’s feelings with the different titles you get when you set your expertise level.

 

PLAY WITH THE PROTOTYPE

 

What’s behind the scenes?

If you look at this PDF, some of the content is static (background), while others are dynamic: name, title, description, image of your sign. The PDF is created on the fly from the data you capture in Storyline.

How does this work?

The Storyline part is obvious, you use the built-in functionality to capture whatever information you need to such as name, description, expert level, etc. These values are stored in Storyline variables. The only “trick” you need is a WebObject you insert to your Storyline.

What’s the WebObject for? The PDF creation on the fly is done in JavaScript, specifically, a cool library fromΒ pdfmake.org. This javascript library allows you to create PDF content on the fly. You can manipulate text, tables, paragraphs, columns, images, etc. It is not a “drag-and-drop” exercise, you do need a little patience and experience with html-like content editing. You will need two files from pdfmake.org, and that is why we have the WebObject in the Storyline file.

  1. Download the two required javascript files from pdfmake.org: pdfmake.min.js and vfs_font.js.
  2. Create an empty index.html file (Storyline requires an index.html file in a folder when you insert a webobject anyway).
  3. You’ll use the index.html file to include the two javascript files AND to create the PDF.
    <!doctype html>
    
    <html lang='en'>
    
    <head>
    
    <meta charset='utf-8'>
    
    <title>my first pdfmake example</title>
    
    <script src='pdfmake.min.js'></script>
    
    <script src='vfs_fonts.js'></script>
    
    </head>
    
    <body>
    
    <script>
    
    var docDefinition = { content: 'This is an sample PDF printed with pdfMake' };
    
    // open the PDF in a new window
     pdfMake.createPdf(docDefinition).open();
    
    </script>
Β  Β  Β  Β  Β </body>

</html>

4. Insert this folder with your index and js files into Storyline as a WebObject.
Resize the WebObject and move it offscreen, users are not going to see it, we just need it, so Storyline would publish the files. (When you insert the WebObject, select the FOLDER, not the index.html file)

5. Publish your masterpiece and see what happens.

This is a very rude implementation. Technically, the PDF maker files have nothing to do with your Storyline at this point, there is no communication between them. Also, creating a PDF right away when the index.html file loads is not too smart anyway. This is just for testing. What should happen is as you start your Storyline file, it opens a new window. It will be blank for a while, and then a PDF appears with one sentence in it. Pretty exciting.

How to make this actually useful?

The thing is, you’ll need to play with the examples on pdfmaker.org to understand how to create content for your PDF. Start with this tutorial:Β http://pdfmake.org/#/gettingstarted and explore the playground on the site. Once you have a solid understanding what you need to do, you can return to your WebObject files and work on the communication between Storyline and the PDF.

There are two challenges you need to tackle:

  1. When you click the Create PDF button in Storyline, somehow, you need to tell the PDF maker to start working on it.
  2. When the PDF maker starts “working on it,” it needs to get the data from Storyline (variable values).

Let’s start with #2.

In the index.html (in theory, javascript probably should be in a separate file but for now, let’s just make it simple and add the javascript in the index file itself), you will create a function. A function has a name and some stuff inside that runs when you “call” the function. The good thing about a function that “stuff” inside won’t execute until you tell the function to do. Therefore, it’s a good solution, since we have to wait until we push a button in Storyline (as opposed to when the page loads).

Let’s name it createPDF! So, you modified javascript part in the html will look like:

<script>
function createPDF()
{

var docDefinition = { content: ‘This is an sample PDF printed with pdfMake’ };

pdfMake.createPdf(docDefinition).open();
}

</script>

The code above will only run when you call the createPDF function. The other thing we need inside this function is to get Storyline variables, so we can add the name to the PDF, for example. When Storyline publishes this WebObject, it will be inside an iframe. It’s like a window in a window. We know that in the “parent” window, which is Storyline, there is a function we can use to get variables:

var player = GetPlayer();

But this GetPlayer() does not exist inside the iframe, where the PDF maker is. To be able to access it from this “child” frame, we modify the code slightly:

var player = parent.GetPlayer();

Now, we have the player object inside the iframe to access Storyline variables:

var player = parent.GetPlayer();

var name = player.GetVar(“Name”);

This assumes that there is a Storyline variable, called Name that contains the value we need. So, here’s the whole function altogether:

 

<script>
function createPDF()
{Β 

var player = parent.GetPlayer();

var name = player.GetVar("Name");

var docDefinition = { content: 'This is an sample PDF printed with pdfMake for '+name+"!"Β };

pdfMake.createPdf(docDefinition).open();
}


</script>

Note that in the docDefinition we added the javascript variable, “name”,Β  to the sentence. Now, the static statement will be customized based on the name people enter in Storyline.

Finally, let’s address #1 issue: how to tell the PDF maker to do this magic? How to call that createPDF function?

The thing is, if you put createPDF(); inside a JavaScript trigger in Storyline when the user clicks a button, it will throw an error. Why? Because the createPDF function does not exist in the parent frame where Storyline is sitting. It is in the child frame. The two frames do not talk to each other, their functions and variables are separate.

If you have only one iframe in your Storyline project, you can access its functions by the doing following:

window.frames[0].frameElement.contentWindow.createPDF();

Add this to the Execute JavaScript trigger on the button in Storyline. When the user clicks on the button, Storyline will call the createPDF function inside the child iframe.

You can create elaborate PDF content with way by adding customized information from Storyline.

IMPORTANT: How Storyline handles WebObject is that when you add a folder (insert it for the first time), it actually copies the content of the folder INTO your .story file. The good thing about it is that every time you publish (even if the original folder is gone), it has the files. However, the bad thing about this is that if you update anything in the folder, Storyline WILL ignore the changes and use the original files. Now, you would think you’re smart, and after updating your index.html with some code, you would go to Storyline, edit the WebObject and select the folder… Do not! If the folder name is the same, Storyline ignores your new files completely, and again, uses the original ones. The only way to force Storyline to load the new, updated files from the folder is to change the name of the folder OR delete your WebObject and insert a new one.

I know it’s a lengthy warning but it happened to me and others many times that we updated something in a file and forgot about this. Republished the storyline content and the changes will never showed up because it was using the original files.

In practice, what I usually do is publish the Storyline. Then I open the published webobject folder and edit the files directly. Refresh the browser, test. Edit the files. Refresh the browser, etc. TroubleshootingΒ  this way is much faster than republishing things from Storyline. BUT when finally debug is done, I have to copy the updated webobject files from the published folder to the original folder, and rename the folder. Now, I can edit the WebObject in storyline, and select the newly renamed folder.

BONUS thoughts: in my example, you set a slider to show your expertise level. This number is translated into levels like guru, ninja, etc. Basically, you need some logic to set a variable based on the slider value. This can happen in Storyline (it would take you 10 triggers, as each number from the slider would set the variable to a different text), or it can happen in javascript (with 3 lines).

var expert = player.GetVar("Slider1");
if (expert>9) expert = 9;
var levels = ["Total Loser","Evangelist","Maven","Guru","Expert","Ninja","Rock Star","Warrior","Growth Hacker","Aficionado"];

And then I can use the levels[expert] anywhere to get the right name based on the expert level number. (The mysterious second line is there because for some reason, the slider that is set to 0 – 9 returns 10 sometimes.)

Download the Source

Download the source files here.