Forum Discussion

JohnCooper-be3c's avatar
JohnCooper-be3c
Community Member
3 months ago

Rise 360’s new Custom Code Block — how far can it go to replace the need for Storyline blocks?

Over the last few years we have completed numerous RISE projects which used Storyline blocks to add different kinds of additional functionality and interactions. So we were excited to learn about the new Custom Code Block and to see what it can (and cannot) do...

There is no doubt it provides a simpler (and less resource intensive) option for including many simple learner engagements and interactions directly in the RISE framework. But we thought we would give it a bit of a workout to see if it could replace the Storyline in one of our most popular client requests - capturing learner notes and including them in a downloadable pdf at the end of the course.

We have a simple demo on our website that needed a bit of updating - it is a RISE course with three lessons and, at the end of each lesson, the learner is invited to enter their own notes which are stored in local browser storage to be retrieved by some Javascript coding to open a pdf notebook template, insert the learner's own notes and offer the new pdf for download:

Demo - Downloadable Notes using Storyline and Local Browser storage

The challenge was - Could we do the whole thing using custom code blocks?

We were genuinely impressed at how simple it was to capture text input using the Custom Code Block. Here’s a simplified version of what one of the note-taking blocks looked like:

<textarea id="notesInput" placeholder="Type your notes here..."></textarea>
<button id="saveBtn">Save</button>
<p id="statusMsg">Saved ✅</p>

<script>
(function(){
  const key = 'eNotes1';
  const input = document.getElementById('notesInput');
  const msg = document.getElementById('statusMsg');
  input.value = localStorage.getItem(key) || '';
  document.getElementById('saveBtn').onclick = () => {
    localStorage.setItem(key, input.value.trim());
    msg.style.opacity = 1;
    setTimeout(()=> msg.style.opacity = 0, 1500);
  };
})();
</script>

Each block is independent and self-contained.
Once saved, that data is visible to any other Rise or Storyline block that runs in the same browser session, because they share the same domain and localStorage space.

Data persistence worked perfectly — notes and learner name fields reappeared even when the course was reopened.

However, In our Storyline demo, the final slide uses the excellent pdf-lib JavaScript library to fill a PDF template and download it using the tiny-save-as library:

const { PDFDocument } = await import('https://cdn.jsdelivr.net/npm/pdf-lib@1.17.1/dist/pdf-lib.esm.js');
const formUrl = 'notebookTemplate.pdf';
const pdfBytes = await fetch(formUrl).then(r => r.arrayBuffer());
const pdfDoc = await PDFDocument.load(pdfBytes);
const form = pdfDoc.getForm();
form.getTextField('pNotes1').setText(localStorage.getItem('eNotes1') || '');
...

Because Storyline runs outside of RISE’s strict sandbox, it can freely:

  • Fetch and modify local files,
  • Generate a new PDF,
  • And open a Save As dialog for the learner.

When we tried the same logic directly inside a Custom Code Block, RISE displayed a success message — but the file never actually downloaded.
That’s because the sandboxed iframe environment doesn’t allow blob downloads or file system access.

Our modified demo below uses Custom Code Blocks for data capture but the original Storyline Block for the final pdf file creation and download:

Demo - Downloadable Notes using Custom Code Blocks and Local Browser storage

The other limitation was that the 'Continue' divider blocks did not wait for completion of the preceding Custom Code Block even though that setting was selected. We found workarounds to this problem but it is definitely a feature we would like to see in future releases.

In summary:

The new Custom Code Block is a fantastic addition to Rise 360 — it finally gives developers a way to embed real interactivity and persistence without leaving the platform.

For me, it opens up a lot of flexibility for client projects:
We can now build lightweight interactive elements directly in Rise, and still use our existing Storyline-based tools (like the Notes-to-PDF generator) where more advanced browser functionality is needed.

It’s not yet a full Storyline replacement, but it’s a strong step in that direction.

11 Replies

  • Hello,

    Luv the sample above. I experimented with the code you used to capture the notes in Rise Code block, but it only created the text entry field far to the left with no way to center it and have it look like your Rise version.

    Was there other "trickery" you did in your Rise version of the note capture?

    I'm loving learning about this new block. Thank you for sharing.

    Lisa

    • JohnCooper-be3c's avatar
      JohnCooper-be3c
      Community Member

      Hi LisaAnderson-57​ 

      Sorry that was a very simplified example block I posted and, as you say, nothing like the actual code I used - here's the actual code block I:

      <link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;600&display=swap" rel="stylesheet">
      
      <div style="font-family: 'Quicksand', sans-serif; max-width: 700px; margin: 0 auto; padding: 1rem; background: #f8f9fa; border-radius: 12px; box-shadow: 0 2px 6px rgba(0,0,0,0.1);">
        <h3 style="margin-bottom: 0.5rem;">📝 Your Notes</h3>
      
        <textarea id="notesInput" rows="8"
          style="width: 100%; padding: 0.75rem; border-radius: 8px; border: 1px solid #ccc;
          resize: vertical; font-size: 1rem; font-family: 'Quicksand', sans-serif;"></textarea>
      
        <!-- Flexbox row: Saved! stays left, Save button stays right -->
        <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 0.5rem;">
          <p id="statusMsg"
            style="
              font-size: 0.9rem;
              color: #28a745;
              opacity: 0;
              transition: opacity 0.4s ease;
              margin: 0;
              min-width: 60px;   /* ✅ reserves space so button doesn't move */
            ">Saved!</p>
          <button id="saveBtn"
            style="background-color: #0066ff; color: white; border: none; border-radius: 6px;
            padding: 0.5rem 1.25rem; font-size: 1rem; cursor: pointer;
            font-family: 'Quicksand', sans-serif;">Save</button>
        </div>
      </div>
      
      <script>
      (function() {
        const textArea = document.getElementById('notesInput');
        const saveBtn = document.getElementById('saveBtn');
        const statusMsg = document.getElementById('statusMsg');
      
        // Each lesson uses a unique key (example: eNotes1, eNotes2, eNotes3)
        const STORAGE_KEY = 'eNotes1';
      
        // Load existing note (if any)
        const savedNote = localStorage.getItem(STORAGE_KEY);
        if (savedNote) textArea.value = savedNote;
      
        // Save on button click
        saveBtn.addEventListener('click', () => {
          localStorage.setItem(STORAGE_KEY, textArea.value.trim());
      
          // Fade in "Saved!"
          statusMsg.style.opacity = '1';
          setTimeout(() => statusMsg.style.opacity = '0', 1500);
        });
      
        // Optional: auto-save while typing
        textArea.addEventListener('input', () => {
          localStorage.setItem(STORAGE_KEY, textArea.value.trim());
        });
      })();
      </script>

       

  • Amazing what you are doing here. Thank you for sharing, John. The coding of the Storyline Block (notes to PDF) is not shared. I presume because it is a building block in Articulate that you cannot share as a code, right?

    • JohnCooper-be3c's avatar
      JohnCooper-be3c
      Community Member

      ClaudiaGruarin​ I have shared the Storyline JavaScript code to do this on this site - but some time ago - AND, the code I shared is old, and now has an issue with some browsers because of the JavaScript code repository the libraries were loaded from - I also had two free courses on my website last year that went through this in detail for (a) Storyline and (b) RISE. These have been withdrawn awaiting an update - I will get a wiggle on and finish at least the new FREE Storyline course ASAP - in the meantime, if you need it urgently, send me a personal message and I will give you a sneak preview! 

  • I am not in a hurry, John, just staying tuned on the newest developments and learn. So no worry, take your time and I will follow your course when it is ready. Thank you so much for your reaction. 

  • JohnCooper-be3c​

    Hi John,

    I applied the updated code, thank you, and it worked beautifully. I didn't have an interest to save to PDF, so I didn't pursue further. 

    Here's what I did notice. I tested using the code for additional reflections in different lessons in a Rise course and when I entered my test text entry in one lesson and then went to the next lesson with the reflection code, I saw my text entry in the reflection text box from the previous lesson. When I inserted new text in the 2nd reflection then went back to the first, my text entry from the second lesson carried over to the first.

    I tried some other code assistance, but to no avail, could we solve it. Do you happen to have any ideas on what might clear the reflection entry for each time the reflection appears in the course?

    Just thought I'd try here.

    Thank you! Luv your work!

    Lisa

    • JohnCooper-be3c's avatar
      JohnCooper-be3c
      Community Member

      Hi LisaAnderson-57​ 

      If you are retrieving the previous lesson's text input it may be that you aren't changing the stored variable name for each lesson. In the code there is a line:

      // Each lesson uses a unique key (example: eNotes1, eNotes2, eNotes3)

      const STORAGE_KEY = 'eNotes1';

      You need to change this for each lesson. So lesson 1 would be eNotes1, lesson 2 would be eNotes2 and so on.

      Or am I misunderstanding the problem??

      Best regards, John

      • KathryneMagn387's avatar
        KathryneMagn387
        Community Member

        This is so exciting to explore. I haven't dipped my toe into capturing notes for export to PDF, either in Storyline or Rise yet. Rise would be so much simpler. Do we feel confident that this approach is stable? I saw your note that you had to take two courses down because the feature stopped working over time. 

  • MarkRash865's avatar
    MarkRash865
    Community Member

    You and I are doing some similar work with allowing learners to complete PDFs via pdf-lib! I ran into the same issues as you with the sandboxed code block preventing the PDF download. I found a couple of workarounds, but unfortunately, they broke once I got to the SCORM point (since by then, we're running an iframe in an iframe in an iframe in a... you guessed it... iframe). 

    The most reliable solution I've found so far is to use my custom code as a web object in a Storyline project instead of a code block. I realize that takes away the automatic vertical sizing of the code block, so it won't work in all use cases. But if your app has a relatively consistent height and you can live with it running in a fixed Storyline block, you can have your custom app do all the fancy work, pass the PDF details as a JSON payload using postMessage, and set a Storyline variable to flag completion. Essentially it works like this:

    1. Create a Storyline true/false variable and a trigger that completes the course when that variable changes to true.
    2. In Storyline, a JavaScript trigger loads and calls pdf-lib and save-as to get and fill the form and facilitate download, just as you would if you were making a purely Storyline-based interaction.
    3. In the same JavaScript trigger, add an event listener to the window to listen for postMessage of the types you define. One message type would be for the JSON payload sent from your app, and the other message type would be for completion and would set the Storyline completion variable to true.
    4. In your custom app (which will be in the web object), pass your two messages (the data for filling the PDF and the completion status) using postMessage.

    I've done a lot of work with PDFs in my learning interactions, but for whatever reason, I have not been using localStorage to retain data between sessions or use it later in a course. Do you find that to be a reliable way to go about it for most users/browsers/devices? I have always just included verbiage in my lessons that the learner had better finish and download their PDF before they close the lesson. 😀 Maybe I could tap into localStorage to help mitigate some of that - and to refer back to previous data later in a lesson.

    Edit to add: I realize the whole point of your post was to see how much a code block can do to prevent the need for relying on Storlyine - and I just said the main fix I found was to use Storyline. Ha! I guess it was a long-winded way of agreeing with you that more advanced PDF stuff may still require Storyline. But my main point was also that it's possible to build a custom interaction and wrap it in Storyline to get around the sandbox problem for downloads.

    • JohnCooper-be3c's avatar
      JohnCooper-be3c
      Community Member

      Local Browser Storage works fine if you accept the limitations - the data is stored in that browser's local memory. - So if you change browser half-way through the course OR if you change system - you lose the data. OR also if you clear the browser cache!!

      I have done projects where I have stored the data in the SCORM 2004 database (there is a standard defined field in SCORM called "comments_from_learner") it worked fine and was more persistent than local browser memory - but the JavaScript was brutal!! As you say you are running in an Storyline iframe within a RISE iframe within a SCORM iframe etc. etc. Getting to the SCORM level is tricky. But is does work!