Forum Discussion
Creating downloadable pdf files in Storyline - an update on earlier methods
Hi
Your reply prompted me to tidy the demo up a bit - same link as above.
https://demo5.profilelearning.com
I am more than happy to explain how this is done - and share the Storyline file for this demo. But I would ask a favour in return - we have set up a new LinkedIn company page and will be publishing articles there on all things learning technology - it's new and needs followers!!
https://www.linkedin.com/company/profilelearning/
OK - so the heart of the JavaScript works this way:
Firstly it retrieves the Storyline variables it requires
//Retrieve Player Variables
var player = GetPlayer();
learnerName = player.GetVar("Name");
learnerTitle = player.GetVar("Title");
learnerNotes = player.GetVar("Notes");
the code then dynamically loads the "pdf-lib" JavaScript library using an import() construction. This is part of 'modern JavaScript' and can only be used to load ES format or UMD format modules. By doing it this way you load the library only when the code is executed - you don't need to edit the story,html file to include a "src =" statement...
NOTE: I am loading the library from the on-line code repository unpkg.com and, by default I load the latest version. In a production environment it is always safer to load a specific version in case changes to the latest version messes your code up!
This bit inside the fillForm function uses the pdf-lib code to define a new pdf document instance PDFDocument and then uses a fetch command to read the pdf template file 'NotesTemplate.pdf' into a buffer array - i.e. it just reads it as a set of binary bytes. It then uses the pdf-lib 'load' method to create a pdf document pdfDoc and then the .getform method to read the pdf form data (fields etc.) from this pdf into a form called 'form'
//Read pdf form
const { PDFDocument } = PDFLib;
const formUrl = 'NotesTemplate.pdf';
const formPdfBytes = await fetch(formUrl).then (res => res.arrayBuffer());
const pdfDoc = await PDFDocument.load(formPdfBytes);
const form = pdfDoc.getForm();
NOTE: the pdf template 'NotesTemplate.pdf' in this code is read from the root directory - so you will have to copy it to the root (where the story.html file is) after you have published the course - you could put it on a server with public access to the file...
Now you can create variables for each fieldname you want to fill in within the form (you need to know what fieldnames you have created in the pdf form template - in this case LName, LTitle and LNotes)
//Get field names
const nameField = form.getTextField('LName');
const titleField = form.getTextField('LTitle');
const notesField = form.getTextField('LNotes');
You can then write your variables into the the newly created pdf form:
//Fill in form
nameField.setText(learnerName);
titleField.setText(learnerTitle);
notesField.setText(learnerNotes);
The rest of the code converts the form back to a byte array and then uses another JavaScript library 'tiny-save-as' which is, again called dynamically. The file is output as a binary 'blob'.
I've attached the story file and the NotesTemplate.pdf form file. Just publish to the web - copy the pdf template into the root folder and it should work.
NOTE: you can modify the pdf template and add all sorts of stuff in it without changing your JavaScript code - as long as you don't change the field names. And, yes, it could be a multi-page document.
I know this looks daunting if your JavaScript is not so good - but, trust me, it is a lot easier than the previous methods I have used for multi-page pdf's.
Thank you for sharing this method, John! It has greatly enriched my process for exporting course participant entries into a PDF file.
Paired with this thread with solutions posed by Steve Gannon (https://community.articulate.com/discussions/articulate-storyline/pdf-publishable-certification-checklist), I've been able to "export" both TextEntry and CheckBox data to PDF.