Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make tutorials printable #465

Open
siebrenf opened this issue Jan 8, 2021 · 5 comments
Open

Make tutorials printable #465

siebrenf opened this issue Jan 8, 2021 · 5 comments

Comments

@siebrenf
Copy link

siebrenf commented Jan 8, 2021

We're developing a set of tutorials for a remote course. At the end of the course, there will be some form of open exam, during which students can bring their own notes. It would be nice for students to save their (entire) current tutorial to PDF/static HTML.

Notes:

Knitr seems to allow for this in pure markdown, JavaScript seems to allow for this for Shiny, but I haven't been able to get this to work in a learnr tutorial. Please note that I barely understand anything related to js.

Here's the list of methods which don't seem to work:

  • saving the browser page manually
  • any shinyjs implementation (does not seem to work with shiny_prerendered documents)
  • calling jsPDF() in a javascript chunk
  • calling html2pdf in a javascript chunk

The best I've come up with is an actionButton to trigger a js chunk with some non-functioning code to save to PDF.

    ```{js, context="server"}
    $(document).on('shiny:inputchanged', function(event) {
      if (event.name === 'printPdf') {
        alert('JavaScript called!');
    
        var doc = new jsPDF();
        var specialElementHandlers = {
            '#editor': function (element, renderer) {
                return true;
            }
        };
    
        doc.fromHTML($('#content').html(), 15, 15, {
          'width': 170,
          'elementHandlers': specialElementHandlers
        });
        doc.save('sample-file.pdf');
    
        alert('Save done!');
      }
    });
    ```

    ```{r, context="render"}
    actionButton("printPdf", "print to PDF")
    ```

Related SO posts:
rmarkdown cache management from shiny_prerenderd to static runtime
Export Shiny page to PDF
Is there a “Save Page As PDF” for Shiny app?
Is it possible to save HTML page as PDF using JavaScript or jquery?
Generate pdf from HTML in div using Javascript
Is there a way to use a Javascript package in R?

@gadenbuie
Copy link
Member

Here's an intermediate solution using CSS and print @media queries. Using the CSS below, printing the tutorial from the browser will show all of the content from the tutorial (with some caveats below):

@media print {
  .topicsContainer,
  .topicActions,
  .exerciseActions .skip {
    display: none;
  }
  .topics .tutorialTitle,
  .topics .section.level2,
  .topics .section.level3:not(.hide) {
    display: block;
  }
  .topics {
    width: 100%;
  }
  .tutorial-exercise, .tutorial-question {
    page-break-inside: avoid;
  }
  .section.level3.done h3 {
    padding-left: 0;
    background-image: none;
  }
  .topics .showSkip .exerciseActions::before {
    content: "Topic not yet completed...";
    font-style: italic;
  }
}

You can insert this CSS into a single tutorial with a CSS markdown chunk

```{css echo=FALSE}
/* paste CSS above here */
```

or you can store it in a file, say print.css, and import it with the css argument of learnr::tutorial():

---
output:
  learnr::tutorial:
    css: print.css
---

Caveats

Generally, printing from the web works best on Chrome, but Safari and Firefox also fare well. Here's an example of a complete tutorial printed from Safari: example-printed-tutorial.pdf

The user's content will appear in the printed version, and fortunately learnr saves the past tutorial state so a user could return to a completed tutorial and print their work. They will, however, need to visit each topic within the tutorial for all of the content to be populated, so it's probably best to nudge users to print at the end of the tutorial.

The CSS above will also hide sections that haven't been shown yet if you've enabled progressive: true. I added a small message that would hint to a user that they haven't completed the topic yet. If you wanted the incomplete material to show up anyway, you can remove the :not(.hide) bit of the last selector in the second CSS rule.

Let me know if this works for you and we'll consider incorporating this into the package.

@gadenbuie gadenbuie changed the title Save to PDF Make tutorials printable Jan 8, 2021
@siebrenf
Copy link
Author

siebrenf commented Jan 8, 2021

Thank you for the insight! I've run this with two tutorials and two browsers and it seems to be performing well enough on both Chrome and Firefox (I'll ask my colleague to try it on her Mac next week). The only glitch is that answers and dataframes are lacking their colors, but personally that seems fine. The "Topic not yet completed..." warning is excellent.

image
image

As you already indicated, providing the option to print is not strictly necessary, but it provides users with more options to review their material, and saves our server some load (especially during the exam)!

For potential incorporation in learnr: finding and using each browsers "print..." option is finicky (it took me embarrassingly long find out how to trigger the CSS code :p ).
Using my test code above I can call js window.print() when the actionButton is pressed. Ideally, it would be a button in the tutorial-format.js topicsList, similar to the "start over" button.

@andysouth
Copy link

@gadenbuie @siebrenf

Thankyou for this which seemed like it could solve a use-case we have. We provide learnr tutorials on shinyapps and want to be able to provide a backup pdf option for learners who have unreliable internet.

I tried following your suggestion of putting into print.css. But printing the shinyapps page from Chrome just results in the first topic (afrimapr/afrilearnr#10) rather than all topics as you show in your example.

This message in the chrome developer window suggests possible source of problem. Apologies I am not well versed in web development, css etc.
image

Suggestions welcome. Thanks.

@siebrenf
Copy link
Author

No idea what the error is! I'll post the code that worked for us below. We tested this on Rstudio and Rstudio Server.

    ```{js print2pdf1, context="server"}
    // the following 2 chunks print the completed sections of the tutorial to PDF
    // uses "css/print2pdf.css"
    $(document).on('shiny:inputchanged', function(event) {
      if (event.name === 'print2pdf') {
        window.print();
      }
    });
    ```
    
    ```{r print2pdf2}
    # button can be placed anywhere in the tutorial
    actionButton("print2pdf", "Print page", style="opacity: .7; color: #000;")
    ```

@andysouth
Copy link

Thanks @gadenbuie @siebrenf, eventually I got this to do what I wanted by just using the css chunk in each Rmd see afrimapr/afrilearnr#10 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants