I know there are many excellent examples of how to create a pdf file in Storyline using javascript libraries such as jspdf and I have done this successfully in projects compiled for the web...
...but has anyone tackled this with an lms version? I know you could pass data to the lms using xAPI - but I really just want to click a button and "save" the pdf file created within the course using variables collected during the course.
I'm wondering if I could include the FS javascript library, do an asynchronous write to create the file somewhere, and then jump to the pdf file? Or is that crazy??
What I'm doing is getting the learner to complete a questionnaire using sliders - I capture their responses and then create a pdf using the javascript jspdf library with their responses in it on a kind of grid .
I then allow the learner to be able to download the pdf with the results in - this is done using the javascript Filesaver library.
The Storyline works fine if loaded to a webpage.
If I compile the course for an lms (and make the necessary changes to include the javascript libraries I'm using) and then run this code from a website (or even my own pc, as I'm not loading an image), It also works fine - I get the prompt to open or save the pdf (see attached screen - client name redacted).
But obviously, the run environment is different when the same code is being run under an lms - I have tested the code loaded into Moodle and the learner doesn't get the prompt.
There's clearly a difference in the way the lms displays the course - presumably in some form of frame - I just wondered if anyone had taken a look at this and could steer me in the right direction?
Can I be really cheeky and ask another question? I have the pdf creation working great - thanks to you! But I'm now experimenting with adding a background image to the pdf using doc.addimage in the javascript.I have the necessary jspdf libraries loaded (I think) but I'm running into a CORS problem - I've checked other examples (like Devlin Peck's tutorial) and have included a img.crossOrigin statement just before declaring the image source (the image is in the output folder):
img.crossOrigin = "";
img.src = "Inventory.png";
BUT - I still get an error when I try and load the image, The error triggers the img.onerror = function {.......... code I have added
var doc = new jsPDF();
var img = new Image;
img.onload = function() {.........
....
img.onerror = function(e) {....
The error message I get in the debug console is:
Access to image at 'file:///C:/Users/John/Documents/CURRENT/xxxxxxxxxxx/xxxxxxxxxxx%20-%20Storyline%20output/Inventory.png'' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome-extension, edge, https, chrome-untrusted.
I'm using jspdf v1.5.3 and have tried loading the .debug version exactly as in Devlin's example)
Again your advice sorted my problem out instantly - I was being a complete dork! Of course I needed the image on a server - which, if I was testing my code on a web server, instead of on my local system, it would be! There are times I can be so dumb!
I guess the clue was in the error message "Cross origin requests are only supported for protocol schemes: http...." didn't really need to read further.
Loading the code I already had onto my server meant it ran perfectly... background image successfully added to pdf - just have to line my text up with the form, which is what the background image is and I'm good to go.
Hi John and Matt. I've used the Base64 method for background images but haven't tried the server method. What would that code look like? Apologies if this is super basic JavaScript.
On a related note, if I want to reference the online version of jsPDF instead of packaging it with the course, how do I do that? Do I add some version of the code below to the story.html file?
Do either of you know how to use a web object to avoid having to edit the story.html file after publish? I saw a post about it a while back but didn't fully understand it.
So "Inventory.png" is the name of my background image for my pdf and needs to be in the storyline output root folder. I am creating an A4 document in portrait. If you want to create a landscape form just swap the "210" and "297" parameters to load your png image and add 'landscape' to the new jsPDF line:
You should be able to use the construct you have given to load the latest version of jsPDF as you suggest.
But a bit of a heads up - some of the examples you will see posted here on heroes pre-date V2.0.0 of JsPDF - (in fact I use jsPDF V1.5.3) - So whereas I would load 'jspdf.debug.js' you, correctly, are loading "jspdf.umd.min.js"
As per the release notes for v2.0.0:
"We renamed the files in dist for consistency: jspdf.debug/min.js is now jspdf.umd(.min).js"
I mention this in case anyone else is confused when they look at earlier code examples.
Having re-read you question - I guess I should also point out that my original error was not my code - but that I was trying to run the code above from a compiled version of my Storyline course on my local PC.
That won't work because the browser is complying with with the CORS (Cross Origin Resource Sharing) security protocol designed to prevent malicious files being loaded. Ironically, files on the same PC accessed via a file reference i.e. no "http: address" are not assumed to come from the same "origin" and therefore trigger a CORS error when the HTTP request to load the image (addImage) is executed.
When I moved the code and ran it on a server (I have my own cloud server but you could use Amazon or Microsoft Azure Cloud storage) the code runs fine because the file is accessed via it's http: address and the img.CrossOrigin ="": command basically tells the browser to accept an 'anonymous' unauthenticated image i.e. switch off the CORS check in the HTTP request.
Sorry that got a bit technical. In simple terms, you have to load the compiled web code with the image in the root folder onto a server for it to work - it won't work if you run it on your PC.
Thanks for the explanation, John. I've come accross a CORS error once or twice and wasn't sure what it meant. I've been using FTP for testing, as custom fonts seem to cause problems locally, too.
One more question, if I adapt the code you shared earlier and want to have different background images on each page of a multipage PDF, what would you suggest? I know how to generate new pages and suspect I would name images sequentially (page1.png, page2.png, etc.). Not quite sure of the other step, though...
I haven't had the need to create multi-page pdf's yet but I see no problem in doing what you suggest. Just generate a new page and then use addimage.
Coincidentally, I submitted a proposal last week to a client that will require multi-page pdf's - so I may have to come to you to find out how to do it :)
I'm also looking at a more extensive javascript library pdf-lib which allows you to modify existing pdf files including filling out pdf forms. It looks really interesting. If I make progress with it I will create an article and publish it here.
I'm happy to help, though I suspect you're much more knowledgeable about all this.
And I'm curious to hear about your experience with the other javascript library. If it has the possibility to generate accessible PDFs, that would be especially appealing.
Just started work on a multi-page pdf document to be produced at the end of a course for my client. The requirement is that there are four screens in the course where the learner is asked to "reflect" on the section they have just covered and decide what they will do as a result.
Each of these screens has two questions - so there are 8 text responses which I have stored in variables in the course. At the end of the course, the learner will click a button to download a 4-page pdf. Each page has a summary of the section, the questions the learner was asked and their responses.
Code similar to that I published earlier works fine for the first page - the notes page is loaded as an image and the learner responses are positioned on the page to fill in the boxes.
I have added:
doc.addPage('landscape');
to the end of the first page and this successfully throws a pagebreak (there are actually two parametrs to this call - the page size is A4 by default but you can specify a different size as well as the orientation).
OK - not trivial JavaScipt as it turns out - but I have solved it and can successfully produce a multi-page 'Notes' pdf document including the learner's own notes collected from text entry boxes through the course.
I will post a separate article on how to do this here on eLearning Heroes and post the link here when I have done it.
Hey John. Reading through the comments was an emotional roller coaster. Please let me know when you post that article :) I'll be curious to learn from it.
Thanks for the reminder about the article - I will definitely get to it. We have moved forward significantly and done several projects using this approach.
What we have done is develop code that:
A: retrieves the data from the variables in Storyline storing them in a table - one row per page.
B. we then add parameters to each variable in the table - font size, x and y pixel coordinates for the position on the page and the number of characters before we throw a new line
C. The code then processes the table in a loop reading the data associated with each variable creating a new page for each row according to the parameters.
It works fine - and we can produce complex handouts without having to modify the code - just by setting up the parameter table for each project.
So the javascript library src= statements should be in index_lms.html??
Yep, for use in an LMS it should be. There may be another change you need do make... but likely not. Let me know.
Hi Matthew, Wondering if you could guide me.
i'm still getting the actionator::exeJavaScript - jsPDF is not defined error in console despite defining it in index_lms.html. I've also gone so far as to add it in story.html, index_lms.html, and also added a file href under imsmanifest.xml yet everytime i test it still gives me that error.
May i ask what other locations and changes you would need to make as you mentioned?
32 Replies
I'm wondering if I could include the FS javascript library, do an asynchronous write to create the file somewhere, and then jump to the pdf file? Or is that crazy??
Thanks Matthew
What I'm doing is getting the learner to complete a questionnaire using sliders - I capture their responses and then create a pdf using the javascript jspdf library with their responses in it on a kind of grid .
I then allow the learner to be able to download the pdf with the results in - this is done using the javascript Filesaver library.
The Storyline works fine if loaded to a webpage.
If I compile the course for an lms (and make the necessary changes to include the javascript libraries I'm using) and then run this code from a website (or even my own pc, as I'm not loading an image), It also works fine - I get the prompt to open or save the pdf (see attached screen - client name redacted).
But obviously, the run environment is different when the same code is being run under an lms - I have tested the code loaded into Moodle and the learner doesn't get the prompt.
There's clearly a difference in the way the lms displays the course - presumably in some form of frame - I just wondered if anyone had taken a look at this and could steer me in the right direction?
All the best, John
Matthew - great idea - why didn't I think of that?
actionator::exeJavaScript - jsPDF is not defined
(anonymous) @ bootstrapper.min.js:38
Not sure why that is though.....
You are correct - I have. At least the
It works perfectly! Matthew - you are a star!!
Hi Matthew
Can I be really cheeky and ask another question? I have the pdf creation working great - thanks to you! But I'm now experimenting with adding a background image to the pdf using doc.addimage in the javascript.I have the necessary jspdf libraries loaded (I think) but I'm running into a CORS problem - I've checked other examples (like Devlin Peck's tutorial) and have included a img.crossOrigin statement just before declaring the image source (the image is in the output folder):
Wow - swift response - thanks. I will try both of those.
Again your advice sorted my problem out instantly - I was being a complete dork! Of course I needed the image on a server - which, if I was testing my code on a web server, instead of on my local system, it would be! There are times I can be so dumb!
I guess the clue was in the error message "Cross origin requests are only supported for protocol schemes: http...." didn't really need to read further.
Loading the code I already had onto my server meant it ran perfectly... background image successfully added to pdf - just have to line my text up with the form, which is what the background image is and I'm good to go.
Hi John and Matt. I've used the Base64 method for background images but haven't tried the server method. What would that code look like? Apologies if this is super basic JavaScript.
On a related note, if I want to reference the online version of jsPDF instead of packaging it with the course, how do I do that? Do I add some version of the code below to the story.html file?
<script src="https://unpkg.com/jspdf@latest/dist/jspdf.umd.min.js"></script>
Last question :)
Do either of you know how to use a web object to avoid having to edit the story.html file after publish? I saw a post about it a while back but didn't fully understand it.
Hi Mark
My code now looks like this:
//Create pdf
var doc = new jsPDF();
var img = new Image;
img.onload = function() {
doc.addImage(this, 'PNG', 0, 0, 210, 297);
doc.setFontSize(12);
then I add all the text to the document
doc.save('Inventory.pdf');
}
img.onerror = function(e) {
console.log("Error code",e);
}
img.crossOrigin = "";
img.src = "Inventory.png";
So "Inventory.png" is the name of my background image for my pdf and needs to be in the storyline output root folder. I am creating an A4 document in portrait. If you want to create a landscape form just swap the "210" and "297" parameters to load your png image and add 'landscape' to the new jsPDF line:
var doc = new jsPDF('landscape');
should do it
Hi Mark
You should be able to use the construct you have given to load the latest version of jsPDF as you suggest.
But a bit of a heads up - some of the examples you will see posted here on heroes pre-date V2.0.0 of JsPDF - (in fact I use jsPDF V1.5.3) - So whereas I would load 'jspdf.debug.js' you, correctly, are loading "jspdf.umd.min.js"
As per the release notes for v2.0.0:
"We renamed the files in dist for consistency:
jspdf.debug/min.js
is nowjspdf.umd(.min).js
"I mention this in case anyone else is confused when they look at earlier code examples.
Cheers, John
Matt - do you know if the 'jspdf.umd.min.js' has the addimage library code in it?? That was the problem with older versions of 'jspdf.js' as I recall.
Thanks for all of your answers, John. I really appreciate it.
Happy to help.
Having re-read you question - I guess I should also point out that my original error was not my code - but that I was trying to run the code above from a compiled version of my Storyline course on my local PC.
That won't work because the browser is complying with with the CORS (Cross Origin Resource Sharing) security protocol designed to prevent malicious files being loaded. Ironically, files on the same PC accessed via a file reference i.e. no "http: address" are not assumed to come from the same "origin" and therefore trigger a CORS error when the HTTP request to load the image (addImage) is executed.
When I moved the code and ran it on a server (I have my own cloud server but you could use Amazon or Microsoft Azure Cloud storage) the code runs fine because the file is accessed via it's http: address and the img.CrossOrigin ="": command basically tells the browser to accept an 'anonymous' unauthenticated image i.e. switch off the CORS check in the HTTP request.
Sorry that got a bit technical. In simple terms, you have to load the compiled web code with the image in the root folder onto a server for it to work - it won't work if you run it on your PC.
Thanks for the explanation, John. I've come accross a CORS error once or twice and wasn't sure what it meant. I've been using FTP for testing, as custom fonts seem to cause problems locally, too.
One more question, if I adapt the code you shared earlier and want to have different background images on each page of a multipage PDF, what would you suggest? I know how to generate new pages and suspect I would name images sequentially (page1.png, page2.png, etc.). Not quite sure of the other step, though...
Hi Mark
I haven't had the need to create multi-page pdf's yet but I see no problem in doing what you suggest. Just generate a new page and then use addimage.
Coincidentally, I submitted a proposal last week to a client that will require multi-page pdf's - so I may have to come to you to find out how to do it :)
I'm also looking at a more extensive javascript library pdf-lib which allows you to modify existing pdf files including filling out pdf forms. It looks really interesting. If I make progress with it I will create an article and publish it here.
I'm happy to help, though I suspect you're much more knowledgeable about all this.
And I'm curious to hear about your experience with the other javascript library. If it has the possibility to generate accessible PDFs, that would be especially appealing.
Just started work on a multi-page pdf document to be produced at the end of a course for my client. The requirement is that there are four screens in the course where the learner is asked to "reflect" on the section they have just covered and decide what they will do as a result.
Each of these screens has two questions - so there are 8 text responses which I have stored in variables in the course. At the end of the course, the learner will click a button to download a 4-page pdf. Each page has a summary of the section, the questions the learner was asked and their responses.
Code similar to that I published earlier works fine for the first page - the notes page is loaded as an image and the learner responses are positioned on the page to fill in the boxes.
I have added:
doc.addPage('landscape');
to the end of the first page and this successfully throws a pagebreak (there are actually two parametrs to this call - the page size is A4 by default but you can specify a different size as well as the orientation).
I will let you know how I get on...
OK - not trivial JavaScipt as it turns out - but I have solved it and can successfully produce a multi-page 'Notes' pdf document including the learner's own notes collected from text entry boxes through the course.
I will post a separate article on how to do this here on eLearning Heroes and post the link here when I have done it.
Hey John. Reading through the comments was an emotional roller coaster. Please let me know when you post that article :) I'll be curious to learn from it.
Hi Joao
Thanks for the reminder about the article - I will definitely get to it. We have moved forward significantly and done several projects using this approach.
What we have done is develop code that:
A: retrieves the data from the variables in Storyline storing them in a table - one row per page.
B. we then add parameters to each variable in the table - font size, x and y pixel coordinates for the position on the page and the number of characters before we throw a new line
C. The code then processes the table in a loop reading the data associated with each variable creating a new page for each row according to the parameters.
It works fine - and we can produce complex handouts without having to modify the code - just by setting up the parameter table for each project.
All the best, John
Hi Matthew, Wondering if you could guide me.
i'm still getting the actionator::exeJavaScript - jsPDF is not defined error in console despite defining it in index_lms.html. I've also gone so far as to add it in story.html, index_lms.html, and also added a file href under imsmanifest.xml yet everytime i test it still gives me that error.
May i ask what other locations and changes you would need to make as you mentioned?
thank you so much for your time
actionator::exeJavaScript - jsPDF is not defined error
means: .js file is not found or not compatible
create a small example (one slide with one button) with your programming and upload the .story file and your modified scorm package here in the forum