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.

16 Replies to “Articulate Storyline to Create a Customized PDF on the Fly?”

  1. Paul Alders says:

    Hi Zsolt, thanks for sharing this post. It’s an awesome solution for one of my own projects that I am working on. Is it possible to share your story file to have a look at it?

    Thanks, keep up the good work!

  2. Tracy Livingston says:

    I’m assuming this only works with StoryLine 3 since I’ve been trying variations with 360 for several days. It appears he function call and GetPlayer variables are the issue. I can create a string variable fine, but as soon as I try to access my SL variable it breaks the script. Another frustration with Articulate who seem to be moving backward with new releases.

  3. David says:

    Hi Zsolt,
    Thanks so much for publishing all this stuff about JavaScript. I have been going through your pages and learning a lot and how I might use it with storyline.

    I was working through your example on this page and can’t get the example at the top to work. Its the part about just having storyline create a new PDF doc and add “This is a sample PDF printed with pdfMake” to the page.

    I played with the source file you shared and that works fine.. but I wanted to try to start at the beginning and learn from there. Any thoughts about what I might be doing wrong?

    • Hi David,

      When you open the console (Ctrl+Shift+I on Chrome), what error and warning messages do you see? I usually start there.

      Zsolt

      • David says:

        Thanks, Zsolt! I had a put this off to the side for some other work.. you know how bosses can be when you have projects due 😉
        I’m hoping to get back to it this week and I’ll let you know what I come up with using the console

  4. Dan says:

    Hi Zsolt,
    THANK YOU for this post and all your other JS/Storyline posts. Love your POP(99) course! Because my JS skills are only at the “script-kiddie” level, I rely on your contents to build on my JS skills.

    Regarding this post on PDF Make function in Storyline, I’ve downloaded your files and examined the Storyline file and the index.html file contained in the Webobjects folder. I’m unable to see how you’re able to insert the aries/capricorn png files on-the-fly based on the “SelectedSign” Storyline variable.

    Please explain which lines of code in your index.html is used to display the base-64 image that corresponds to the Storyline “SelectedSign” variable value. If not the code in your index.html, then where and how were you able to make this happen.

    Thank you so much for your time,
    Dan

  5. Sumit says:

    This is awesome! Unfortunately, I am getting the error: Blocked a frame with origin “null” from accessing a cross-origin frame.

  6. David Glow says:

    This is interesting- can you have multiple folders with index.html files if you want multiple PDFs in one project?

Leave a Reply

Your email address will not be published. Required fields are marked *