Forum Discussion
Creating downloadable pdf files in Storyline - an update on earlier methods
š³šš¾šš¾ ššŗšš¾ š»š¾š¾š ššŗšš š¾šš¼š¾š š š¾šš ššššš ššš¾š ššš¾ šš¾šŗšš šš š¼šš¾šŗšššš šš½šæ šæšš š¾š š»š ššššš š©šŗššŗš²š¼šššš šæššš šššššš š²ššššš ššš¾ (š¾.š. Saving Storyline Variables to a PDF - Articulate Storyline Discussions - E-Learning Heroes) šŗšš½ šØ ššŗšš¾ ššŗš½ šš¾š š šæššš ššŗšš šš¾ššš¾š šš šššš šššš¾ ššš¾š šØ ššŗš ššššššŗš š š šššššš šš šæššššš¾ šš ššš šæšš šššš¾š šæ.
š¬ššš ššæ ššš¾ šŗšššš¼š š¾š (š ššš¾ ššš¾ ššš¾ šŗš»ššš¾) šŗšš¾ šæšŗššš š šš š½ ššš šŗšš½ šŗš½ššš¼šŗšš¾ ššššš ššš¾ š©šŗššŗš²š¼šššš š¼šš½š¾ š šš»ššŗšš "šššÆš½šæ". š ššššš¼šŗš šŗšššššŗš¼š šššš š½ š»š¾ šš š¼šŗššššš¾ šššš š š¾šŗššš¾š'š ššššš ššššš šš¾šš š¾šššš ššŗšššŗš»š š¾š šš š²ššššš ššš¾ - šŗš ššš¾ š¾šš½ ššæ ššš¾ š¼ššššš¾ ššš ššš¼š šš½š¾ šŗ š»ššššš šššŗš šššššš¾šš šššš¾ š©šŗššŗš²š¼šššš šššš¼š šššš š½ ššš¾š šš¾šššš¾šš¾ ššš¾ š²ššššš ššš¾ ššŗšššŗš»š š¾š, šš¾šŗš½ šš šŗ ššš šš ššš šššŗšš¾ ššæ šŗ šš½šæ šæšš š¾ ššŗšš¾, šššš¾šš ššš¾ šš¾ššššš¾š½ šš¾šš šŗš ššš¾ šŗššššššššŗšš¾ š šŗšš½ š š¼šššš½šššŗšš¾š šš ššš¾ ššŗšš¾, šŗšš½ ššš¾š š¼šŗš š šŗ šæššš¼šššš šš "ššŗšš¾" ššš¾ šš¾ššš ššŗšš š¼šššš š¾šš¾š½ šš½šæ.
š³šš¾ š š¾šŗššš¾š (š½š¾šš¾šš½ššš šš ššš¾šš š»ššššš¾š) šššš š½ š»š¾ ššš¾šš¾ššš¾š½ šššš šŗš šššššš šš 'ššŗšš¾' šš 'š½šššš ššŗš½' ššš¾ šæšš š¾.
š¢šš¾šŗšššš ššš šššš š¾ ššŗšš¾ šš½šæ'š ššŗš š¼šššš šš¼šŗšš¾š½ - š»šš šŗš šš šššššš»š š¾. šØ'šš¾ šš¾šššššŗš š š š½ššš¾ ššŗšš ššš¼š¼š¾šššæšš ššššš¾š¼šš ššššš šššš šŗšššššŗš¼š šæšš š½šššš ššŗš½ššš š¼ššššš¾ šššš¾š, ššššš¾š šš¾ššš šš šš š¼š¾ššššæšš¼šŗšš¾š.
š¦š¼ ššµš š®šŗ š š»š¼š š®š±šš¼š°š®šš¶š»š“ š® š±š¶š³š³š²šæš²š»š š®š½š½šæš¼š®š°šµ?
š¶š¾š š , š ššš¾ ššš¾ šŗšššš¼š š¾š, šššÆš½šæ šš šŗ š šš»ššŗšš šššŗš ššŗš š»š¾š¾š šŗššššš½ šššš¾ šššš¾. šØš š½šš¾š šŗ šššš½ ššš» šŗšš½ ššššššŗšš½š ššæ š©šŗššŗš²š¼šššš ššššššŗššš¾šš šš¾š š šš šš. šØš š¼šŗš š¼šš¾šŗšš¾, šŗšš½ ššŗšš¾ šš½šæ'š šššš šæššš¾. š§ššš¾šš¾š, šŗ šššš¾ šš¾š¼š¾šš š©šŗššŗš²š¼šššš š šš»ššŗšš "šš½šæ-š šš»" ššŗš šŗ š«š®š³ šššš¾ šæššš¼šššššŗš ššš. šØš ššŗšššš¼šš šŗš, šš š¼šŗš
ššš½ššæš š¾ššššššš šš½šæ'š
š¼šš¾šŗšš¾, šæšš š šŗšš½ šæš šŗššš¾š šš½šæ šæšššš
šŗš½š½, šššš¾šš šŗšš½ šš¾šššš¾ ššŗšš¾š šæššš šŗ šš½šæ
...šŗšš½ šŗ šššš š¾ š šš šššš¾.
š³ššš šš¾šŗšš šššŗš ššššš¾šŗš½ ššæ ššŗšššæšš š š ššššššš ššš ššš¾šš¾ ššš šš¾š¾š½ šš šš šŗš¼š¾ ššš¾ šš¾šš šš š¾šŗš¼š ššŗšš¾ ššæ ššš¾ šš½šæ, ššš š¼šŗš ššššš š š½š¾šššš šŗ šš½šæ šæššš, ššŗšš¾ ššš¾ šæšš¾š š½š šŗšš½ ššš¾š ššš¾ ššš¾ šš¾šššš¾šš¾š½ ššŗšššŗš»š š¾š šæššš š²ššššš ššš¾ šš 'šæšš š ' ššš ššš¾ šæššš.
š¶š¾ ššŗšš¾ š»š¾š¾š šŗššŗšš¾ ššæ šš½šæ-š šš» šæšš šššš¾ šššš¾ š»šš šš¾šš¾š šæšššš½ ššš¾ šššš¾ šš š½š šŗššššššš šŗš»ššš šš (ššššš ššš¾ šš š½ ššššššŗššš¾šš' šŗš½šŗšš¾ "ššæ šššš š¼šš½š¾ šš ššššššš š½šš'š ššš šŗšš½ šæšš šš"). š®šš¾š ššš¾ š¢šššššššŗš š»šš¾šŗš šš-š»š¾ššš¾š¾š š¼š¾š š¾š»ššŗššššš (šŗšš½ šššš»šŗš»š š šæšš¾š š¾š½ š»š ššš ššš¼š šæš¾ššššš¾ šššššš šš šš¼š¼šŗššššš) šš¾ šš¾šš¾ šŗš»š š¾ šš šš¾š šŗ šš¾š (š»šš šš¾šš š»šŗššš¼) š½š¾šššššššŗšššš ššššššš šššš¼š š¼šŗš š»š¾ ššš¾šš¾š½ šš¾šš¾:
https://demo5.profilelearning.com
š®š, šššš ššš'š šŗ šš¾šš ššššš¾ššššš¾ š½š¾šš - š»šš šš¾ šŗšš¾ ššššššš šš šš - šŗšš½ ššš š ššš»š ššš ššš šš šššš¾š¾ šššš¾ šŗšššš¼š š¾š š¾ššš šŗššššš šŗ š»šš šššš¾ šŗš»ššš šššš š¼šš½š¾ š šš»ššŗšš šŗšš½ šŗš šš šŗš»ššš 'ššš½š¾šš š©šŗššŗš²š¼šššš'
šššš šš¼ ššµš²š šš¼ššæ š®š½š½š²šš¶šš² š® š¹š¶ššš¹š² š³ššæššµš²šæ šš¼ šÆš¼š¼šøšŗš®šæšø ššµš¶š š½š¼šš - šš² š®š¹šš¼ šµš®šš² ššµš² š®šÆš¼šš² š±š²šŗš¼š»šššæš®šš¶š¼š» šš¼šæšøš¶š»š“ š¶š» š„šš¦š....
- JohnCooper-be3cCommunity Member
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.
- Yvonne-BriljentCommunity Member
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.
- SaraBean-da7305Community Member
Hi everyone. I read the notes on this forum extensively (thank you all!) and have talked to John to work out a bug I was having and LOVE this functionality. I'm including a link to a resource that I made for my team showing how to do this. I'm a senior instructional designer at New York Life and often train my team on different bits and bobs of the technical aspects of the job so I put this little thing together for them and held a short training session.
I'll put a link to download the .story and the PDF file in the comments on Review.
https://360.articulate.com/review/content/3b3e97ba-fa9a-4dcd-a378-aba59cde4898/review
- RobSkeetCommunity Member
This is big news and a welcome addition. It would be wonderful if SL could incorporate this into upcoming versions.
I take it, there is no way to publish this in Review correct?
My project needs to be launched in a SCORM package on an LMS. Are there any issues with launching that way? assuming you add the NotesTemplate.pdf to the root of the zip?
- Dave-RuckleyCommunity Member
When you go to publish to review there's an option for "Publish locally for manual upload". That'll generate a zip file that you can add the PDF template to and then in Review you can upload the file using the option at the top left of the page.
Worked perfectly for me and has made creating PDF's so much easier!
- JohnCooper-be3cCommunity Member
Hi Sherri
The problem is in the pdf file template. Sorry, my fault I should have formatted it correctly.
All you need to do is open the pdf template you are using in Adobe Acrobat - select Prepare Form and right click on the field you want to left justify. Then select Properties and then Options:
You can then click the Multi-line setting - this should left justify the field when it is filled in and allow for text wrapping....
- SherriSagers-caCommunity Member
That works perfectly! Thanks so much for the help on how to edit the form in Adobe, too... I wouldn't have figured that out without your help, either. You're awesome!
- AnnemarieReb048Community Member
Hi John,
That sounds amazing. Can you show how you set up the example or provide the source file? I'm looking for the exact functionality that you are showing here!
Thanks!
- SherriSagers-caCommunity Member
And one last thing! ... for anyone who needs an uneditable PDF...
I didn't want our learners to be able to edit any of the fields (like their scores, etc.) after printing, so I found that I could make the PDF uneditable by "flattening" it. It just takes one line of code right before the code to save the file:
form.flatten();
- RobSkeetCommunity Member
You are a hero among heroes!
How do you go about formatting the PDF to add more fields?
(Yes, I liked your LinkedIn Page!)
- JohnCooper-be3cCommunity Member
Hi Rob
Glad you liked this demo. Thanks for viewing the LinkedIn page.
Adding new fields to the PDF and JavaScript is pretty simple. Just start with the NotesTemplate.pdf - open it with Adobe Acrobat (there are probably other pdf form editors but I only use Acrobat). Then select the "Prepare Form" option. You can now resize the boxes - edit the text - add new fields and even add additional pages if you wish. (see screenshot attached)
When you add new fields you will need to give them a name - make a note of the fields you add. e.g newtemplateField
Now add the text entry or variables you need to capture in Storyline - again note the field names. e.g newstoryField
Then edit the JavaScript - very simple...
var player = GetPlayer();
learnerName = player.GetVar("Name");
learnerTitle = player.GetVar("Title");
learnerNotes = player.GetVar("Notes");newJSField = player.GetVar("newstoryField");
This grabs your new Storyline variable from Storyline and puts it in a JavaScript variable...
Then add the pdf template fields you have added to this section of code that gets the field names from the pdf template:
//Get field names
const nameField = form.getTextField('LName');
const titleField = form.getTextField('LTitle');
const notesField = form.getTextField('LNotes');const newField = form.getTextField('newtemplateField');
Then just add you new variable when you fill the form out:
//Fill in form
nameField.setText(learnerName);
titleField.setText(learnerTitle);
notesField.setText(learnerNotes);newField.setText(newJSField);
Simple! If I've got that right - I always need to check which is the pdf field name, which is the Storyline field name, which is the JS variable I'm using to store the Storyline variable and which is the JS variable I'm using to point to the field in the template.... But once you have your head around that it's simple :)
- IestynRimmerCommunity Member
Hi,
I have followed the steps above but am getting an issue with CORS with it trying to gain access to the local file - No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Would this be fixed by hosting the file on a cloud storage system or is there something else that I'm missing?
Thanks
- JohnCooper-be3cCommunity Member
Hi lestyn
Sorry not to reply earlier.
Yes. I should have made the point that the Storyline example I loaded "NoteTaker.story" doesn't work if you compile it as a web app and then run it on your 'local' device. The problem is precisely as you describe - you get a CORS error.
It's a little ironic really that you cannot access a local data file from JavaScript because of a cross origin resource sharing error (unless that file is being served by a web server on the same system)... Basically it is a security issue. The browser executes the JavaScript and sends an 'Origin Header' with any 'fetch' or HTTP request. If the server's response doesn't include the appropriate CORS header, the browser will not allow the request and throws the CORS error you saw.
If you think about - it would be a little unsafe if I could just execute JavaScript code to access any data file on the local system!
If you load the Storyline output onto a web page on another server (as per my demo) everything will be fine.
There are services like Ngrok you can use to test your code locally if necessary.