Forum Discussion

JasonHalterman's avatar
JasonHalterman
Community Member
4 months ago

Storyline / Javascript Help

Hello!

I am trying (and failing miserably and consistently!) to have Storyline create a downloadable PDF form filled by user input. I've searched through previous discussions, used some of that code, had ChatGPT help me, and I still can't get this to work. When I click the button (that is the trigger to execute javascript), nothing happens. I've tried replacing with a simple line, and the same issue occurs. I've tried uploading to my LMS as a SCORM and trying Rise360 with the storyline file as an interactive piece.


Here's the code:

// Retrieve Player Variables
var player = GetPlayer();
var learnerRCGoal = player.GetVar("RCGoal");
var learnerRCDueDate = player.GetVar("RCDueDate");
var learnerRCActionPlan = player.GetVar("RCActionPlan");
var learnerPPGoal = player.GetVar("PPGoal");
var learnerPPDueDate = player.GetVar("PPDueDate");
var learnerPPActionPlan = player.GetVar("PPActionPlan");
var learnerSMSBGoal = player.GetVar("SMSBGoal");
var learnerSMSBDue = player.GetVar("SMSBDue");
var learnerSMSBActionPlan = player.GetVar("SMSBActionPlan");
var learnerRVGoal = player.GetVar("RVGoal");
var learnerRVDate = player.GetVar("RVDate");
var learnerRVActionPlan = player.GetVar("RVActionPlan");

// Construct PDF when player variables are retrieved
ConstructPdf();

function ConstructPdf() {
  loadPdfLib(); // Load PDF-lib module and fill form
}

function loadPdfLib() {
  // Load pdf-lib library script dynamically
  var pdfLibScript = document.createElement('script');
  pdfLibScript.src = 'https://cdnjs.cloudflare.com/ajax/libs/pdf-lib/1.16.0/pdf-lib.min.js';
  pdfLibScript.onload = function() {
    fillForm();
  };
  document.head.appendChild(pdfLibScript);
}

function fillForm() {
  // Read pdf form from URL
  const formUrl = 'https://drive.google.com/file/d/16GAP-hIeV6_4DL61jsNm7WQZ0xk8jRYh/view?usp=drive_link';
  fetch(formUrl)
    .then(res => res.arrayBuffer())
    .then(formPdfBytes => {
      PDFLib.PDFDocument.load(formPdfBytes).then(pdfDoc => {
        const form = pdfDoc.getForm();

        // Get field objects from the form
        const RCGoalField = form.getTextField('RCGoal');
        const RCDueDateField = form.getTextField('RCDueDate');
        const RCActionPlanField = form.getTextField('RCActionPlan');

        const PPGoalField = form.getTextField('PPGoal');
        const PPDueDateField = form.getTextField('PPDueDate');
        const PPActionPlanField = form.getTextField('PPActionPlan');

        const SMSBGoalField = form.getTextField('SMSBGoal');
        const SMSBDueField = form.getTextField('SMSBDue');
        const SMSBActionPlanField = form.getTextField('SMSBActionPlan');

        const RVGoalField = form.getTextField('RVGoal');
        const RVDateField = form.getTextField('RVDate');
        const RVActionPlanField = form.getTextField('RVActionPlan');

        // Set text values in the form fields
        RCGoalField.setText(learnerRCGoal);
        RCDueDateField.setText(learnerRCDueDate);
        RCActionPlanField.setText(learnerRCActionPlan);

        PPGoalField.setText(learnerPPGoal);
        PPDueDateField.setText(learnerPPDueDate);
        PPActionPlanField.setText(learnerPPActionPlan);

        SMSBGoalField.setText(learnerSMSBGoal);
        SMSBDueField.setText(learnerSMSBDue);
        SMSBActionPlanField.setText(learnerSMSBActionPlan);

        RVGoalField.setText(learnerRVGoal);
        RVDateField.setText(learnerRVDate);
        RVActionPlanField.setText(learnerRVActionPlan);

        var filename = "PhiGoals.pdf"; // Assuming filename is fixed

        // Save the filled form
        pdfDoc.save().then(function(pdfBytes) {
          // Convert bytes to blob
          var blob = new Blob([pdfBytes], { type: 'application/pdf' });

          // Use Storyline's built-in function to trigger file download
          var player = GetPlayer();
          player.DownloadFile(blob, filename);
        }).catch(function(error) {
          console.error('Failed to save PDF:', error);
        });
      }).catch(function(error) {
        console.error('Failed to load PDF document:', error);
      });
    }).catch(function(error) {
      console.error('Failed to fetch PDF form:', error);
    });
}

 

I also have the storyline file and the PDF attached. I appreciate anyone's help.. this is not my area of expertise.

  • Nevermind! I had a computer science student look at it and he fixed something. I'm not sure what, but it works. 

     

    THANK YOU!

  • AndrewHanley's avatar
    AndrewHanley
    Community Member

    HI Jason, sorry Im only replying now, I missed your post!

    I do this sort of thing all the time, and have created my own libraries for this. Its good if you can, because you can tune it to work exactly how you/your clients need.

    However, its quite a bit of explanation required and a lot of different files if you wanted my version.

    Fortunately, another very smart Storyline developer called Tracy Carroll has a freebie PDF generation example on their website. Here is the link:
    https://tracycarroll.net/free-print-certificate-template/

    There is some good explanation, and even a working demo you can test to see if its what you are needing.

    Hope it helps!

  • Hi @Jason Halterman

    I suspect the code you are using is based on a post I did some time ago on this site. Certainly the function names are the same and the basic code structure follows the same pattern.

    I've had a look through your JavaScript and I couldn't see any glaring errors - so I loaded and tested it. You get an error message:

    So, your problem would appear to be in loading the pdf form template from Google Docs. I then tried simply copying the pdf template file to the root Directory and changing where you read the file from:

    This version generated a different error:

    Which would indicate to me that, this time the file was read - But there is an error in one of your "setText" commands where the variable you are trying to write to the file is the wrong data type - i.e. it isn't text.

    I have to own up to disliking Google Docs (irrationally) - so I'm afraid I can't help much with that problem - but the url you supply needs to be the pdf file itself - so it should end in ".pdf" whereas the url you are using doesn't.

    Once you've fixed that you will then need to check that all the variables you are collecting from Storyline are text variables and convert any that aren't. PLUS you need to check that you are not passing any "null" text to the file. Don't let the user leave a field blank.

    Hope that helps??

  • By pure conicidence I loaded a new FREE Advanced Workshop entitled "Creating downloadable notes or a learning journal from a Storyline Course" just yesterday!!

    This course takes the learner through creating a downloadable pdf file step by step using the exact same code (pretty much) that you are using.

    If anyone is interested they can find out more here:

    Course Catalogue (profilelearning.com)

    You will need to enrol on the course and will be sent an email to confirm your identity - but it is free (you will be invite to donate - but that's voluntary :)

  • I know I said I hate Google Docs and couldn't help but I felt bad about it afterwards - Not me - but ChatGPT said you need to make the file url a 'direct download link'

    As per my earlier post - the url should end in ".pdf"... This might work.

  • Nedim's avatar
    Nedim
    Community Member

    A while ago, a friend pointed me to John Cooper's discussion thread about Creating downloadable pdf files in Storyline. Unfortunately, I encountered the same issue with the PDF template from Google Docs. Not wanting to waste time, and knowing that Google Docs was not the only option, I decided to use John's code structure in HTML and import it into Storyline as a WebObject. The PDF template was placed in the same folder as index.html. This setup cannot be tested or run from the local file path, but it works perfectly via a local web server. If this solution works for you, I can post the code I adjusted for your scenario. You can preview it here

    I also want to take this opportunity to express my gratitude once more to John for offering this extensive and informative workshop on this subject. I recently completed a course that I found incredibly valuable, and I would highly recommend it to anyone interested in this topic.

  • Thanks, everyone, for the help!

    @John, I tried the direct download version from GoogleDrive, but that still didn't work. I also tried putting the PDF directly into the root folder, but when I uploaded to Docebo I'm still getting nothing happening when I click the "next" button.  Next step is to get our IT team to upload the PDF directly to our website and try linking from there (what an inefficiency, that i have to go through another person to do basic things.... c'est le vie).

    @Nedim Ramic - can you share the code you wrote? It would be immensely helpful.

    I cannot thank you all enough for your help and guidance! I am determined to get this working.

  • Hi Jason

    Did you fix the other problem?? When I tested your code, I got it to read the pdf template from the root ditectory - but you then get the second error message I posted. Although this message says "Failed to load pdf document" that's because your error handling code says that for ALL errors!

    The error is actually that you are passing a null value in your "setText" command. You need to check if the user doesn't provide a response.

    Try running your app and when you get to the screen with the JavaScript open the browser debug sceen ( F12) in Microsoft Edge. you can then select "console" and you will be able to see actual error (as my post above)

    Good luck

    • JasonHalterman's avatar
      JasonHalterman
      Community Member

      We're getting there!

      1 - Uploading it to our website as a PDF fixed one issue

      2 - Went through all of my variables to ensure there's no null entries (realized there were two "TextEntry" variables used zero times in Storyline. That fixed the other error. Now I'm getting this one:

      actionator::exeJavaScript - Script1 is not defined

      Using this code for downloading:
      //Save form
        const pdfBytes = await pdfDoc.save();
      //Download the form
      await import('https://unpkg.com/tiny-save-as/dist/tiny-save-as.esm.js')
      .then(({default: saveAs}) => {
        const blob = new Blob([pdfBytes], {type: 'application/octet-stream'});
        saveAs(blob, filename);
      })
      .catch((err) => {
        console.log(err);
      });
        }

      I have a screenshot attached and a text document of the entire Javascript code. I cannot thank you enough for helping thus far, and if you can push me over the finish line I will be eternally grateful!

  • Nedim's avatar
    Nedim
    Community Member

    You had an 'await' statement inside fillForm() but it was not declared as async. This code should work if you also fix the variable name (change it to PPDueDate in the variables panel) to avoid the "Error filling form."

    // Retrieve Player Variables
    var player = GetPlayer();
    var learnerRCGoal = player.GetVar("RCGoal");
    var learnerRCDueDate = player.GetVar("RCDueDate");
    var learnerRCActionPlan = player.GetVar("RCActionPlan");
    var learnerPPGoal = player.GetVar("PPGoal");
    var learnerPPDueDate = player.GetVar("PPDueDate");
    var learnerPPActionPlan = player.GetVar("PPActionPlan");
    var learnerSMSBGoal = player.GetVar("SMSBGoal");
    var learnerSMSBDue = player.GetVar("SMSBDue");
    var learnerSMSBActionPlan = player.GetVar("SMSBActionPlan");
    var learnerRVGoal = player.GetVar("RVGoal");
    var learnerRVDate = player.GetVar("RVDate");
    var learnerRVActionPlan = player.GetVar("RVActionPlan");

    // Construct PDF when player variables are retrieved
    ConstructPdf();

    function ConstructPdf() {
      loadPdfLib(); // Load PDF-lib module and fill form
    }

    function loadPdfLib() {
      // Load pdf-lib library script dynamically
      var pdfLibScript = document.createElement('script');
      pdfLibScript.src = 'https://cdnjs.cloudflare.com/ajax/libs/pdf-lib/1.16.0/pdf-lib.min.js';
      pdfLibScript.onload = function() {
        fillForm();
      };
      pdfLibScript.onerror = function() {
        console.error('Failed to load pdf-lib script.');
      };
      document.head.appendChild(pdfLibScript);
    }

    async function fillForm() {
      // Read pdf form from URL
      const formUrl = 'https://sigep.org/wp-content/uploads/PhiGoals.pdf';
      try {
        const formPdfBytes = await fetch(formUrl).then(res => res.arrayBuffer());
        const pdfDoc = await PDFLib.PDFDocument.load(formPdfBytes);
        const form = pdfDoc.getForm();

        // Get field objects from the form
        const RCGoalField = form.getTextField('RCGoal');
        const RCDueDateField = form.getTextField('RCDueDate');
        const RCActionPlanField = form.getTextField('RCActionPlan');

        const PPGoalField = form.getTextField('PPGoal');
        const PPDueDateField = form.getTextField('PPDueDate');
        const PPActionPlanField = form.getTextField('PPActionPlan');

        const SMSBGoalField = form.getTextField('SMSBGoal');
        const SMSBDueField = form.getTextField('SMSBDue');
        const SMSBActionPlanField = form.getTextField('SMSBActionPlan');

        const RVGoalField = form.getTextField('RVGoal');
        const RVDateField = form.getTextField('RVDate');
        const RVActionPlanField = form.getTextField('RVActionPlan');

        // Set text values in the form fields
        RCGoalField.setText(learnerRCGoal);
        RCDueDateField.setText(learnerRCDueDate);
        RCActionPlanField.setText(learnerRCActionPlan);

        PPGoalField.setText(learnerPPGoal);
        PPDueDateField.setText(learnerPPDueDate);
        PPActionPlanField.setText(learnerPPActionPlan);

        SMSBGoalField.setText(learnerSMSBGoal);
        SMSBDueField.setText(learnerSMSBDue);
        SMSBActionPlanField.setText(learnerSMSBActionPlan);

        RVGoalField.setText(learnerRVGoal);
        RVDateField.setText(learnerRVDate);
        RVActionPlanField.setText(learnerRVActionPlan);

        //Save form
        const pdfBytes = await pdfDoc.save();

        //Download the form
        const { default: saveAs } = await import('https://unpkg.com/tiny-save-as/dist/tiny-save-as.esm.js');
        const blob = new Blob([pdfBytes], { type: 'application/octet-stream' });
        saveAs(blob, 'PhiGoals.pdf');
        
      } catch (error) {
        console.error('Error filling form:', error);
      }
    }