PDFRendered: Unable to render a form in its most up-to-date state

Good day everyone!

I am trying to capture a subform Form1.Form_Charts into a pdf and send it via e-mail (just as in the tutorials).

Everything works well except that the PDFRenderer only captures the form with its default dropdown values and plots / images built during the form initialization. If I try use the PDFRendered after changing the plots/images and other objects, the newly printed pdf will still show the form with its default initialized values. Is there a way to ensure that the render_form() captures the most up-to-dated Form_Charts form?

The content in the Form_Charts itself is automatically updated whenever a dropdown or date picker value changes (as shown in the 2nd function below).

The pdf renderer is called manually from the client-side by using a button that calls the create_pdf func on the server-side.

@anvil.server.callable
def create_pdf(file_name):  
  file_name = file_name + '.pdf'
  pdf = PDFRenderer(page_size='A4', 
                   filename = file_name,
                   landscape = False,
                   margins = {'top':1.0, 'bottom': 1.0, 'left':1.0, 'right':1.0},
                   scale = 1.0,
                   quality = 'original').render_form('Form1.Form_Charts')
  return pdf 
def date_picker_2_change(self, **event_args):
    # Assign slected date to glob var
    self.dt_picker2 = str(self.date_picker_2.date)
    # Plot historical data
    self.plotHistoricalData()

Any ideas on what I am doing wrong? Is this related with data bindings in the respective form or perhaps related to the fact that the renderer is being used on the server-side?

Thank you in advance.

Kind regards,
Roserio V.

The defaults are what you’re getting because you aren’t passing the values you want as arguments to the render_form message, as you can see in this Anvil Doc’s examples:

The reason for that is because behind the scenes Anvil isn’t rendering the form on screen, but making a new form entirely from scratch and converting that to a PDF.

1 Like

Hi Duncan,

Thank you for your reply.
In this case, the defaults are programmed such that the form (to be rendered) initialization (which involves retrieving data from a RESTful API) results in a limited data set being requested from the API.

Then, once the form has been initialized, the user can adjust the data set size (date/time interval, ticker/pair, time frame, … for OHLC historical data) and then a large data set is automatically requested from the API. At this point, the desired app behavior is to only update plots and some indicators. Thus, printing a PDF every time an update is triggered is not desired.

On the other hand, the ‘prt PDF’ button is intended to only render the current form into a PDF if clicked. If the PDFRenderer called the respective form using arguments, it would imply that the same data set would have to be called twice from the API, doubling the costs of retrieving data. (*1)

(*1) Unless there is a way to store the same information in a sort of global var and then use it to re initialize the same form using this variables as new arguments. But, in my opinion, it doesn’t seem very efficient the fact that the entire form (calculations and plotting) has to be reinitialized just to render a form that is already being displayed. Isn’t there a way to reference the given form in its most up-to-dated status without having to reinitialized it? I believe that apps that use timers would be facing a similar challenge when using the PDFRenderer at least if the intend is to capture an event at a specific time.

What would be the best way to store data in the server-side as “global” variables without using Tables?

Thank you.
Kind regards,
Roserio

I think you’re misunderstanding how Anvil renders a form to a PDF. You’re not getting the current instance of the form the user is seeing, you’re getting a new copy of the form that has no relation to any form the user might be seeing.

That’s why @duncan_richards12 said to pass information to the __init__ of the form via the render_form call. You have to pass in all the information needed to populate the form via that call.

That might mean your form has to work in two modes. One is user-interactive mode, where it does the API calls to get information to display. The other is pdf-rendering mode, where it takes all the information via __init__.

1 Like

If you want to render what you see on the client to PDF, just press Ctrl+P and let the browser do the job for you.

If you want to ask Anvil to render to PDF, Anvil will do that on the server side, on its own instance of Chrome. This may sound like a waste of resources when you are staring at a form that is exactly what you want to print, but it’s what makes PDF generation powerful and flexible. Rendering PDF on the server allows you to generate reports without having any user to first show them on their browser. Think about the statements page of a bank site: there are tons of buttons to click, each will generate and give you a PDF without first rendering it on your browser.

When you click on the generate PDF button on the client, you should collect all the info that you already have, pack it into a dictionary and send it to the server. Something like:

def generate_pdf_click(self, **event_args):
    pdf_info = {
        'option1': dropdown_1.selected_value,
        'text2': textbox_2.text,
    }
    anvil.server.call('generate_pdf', pdf_info)

Then the form should (optionally) initialize with it:

class MyForm(MyFormTemplate):
    def __init__(self, pdf_info=None, **properties):
        self.init_components(**properties)
        if pdf_info:
            dropdown_1.selected_value = pdf_info['option1']
            textbox_2.text = pdf_info['text2']
1 Like

@jshaffstall
Thanks for the feedback. Indeed, I may have not phrased that very well but the message from @duncan_richards12 was clear and well understood when it comes to how the PDFRenderer works.

While reading your suggestion regarding the two modes approach I got an idea how to circumvent this. Indeed, with a bit of work I can create an hidden form that will look more like a report. The report will be created by clicking a “prt PDF” button. The relevant values from the original “GUI” form are then used as arguments to initialize the new “report” form and render it into a PDF. And, eventually, the pdf will be also automatically saved into a dropbox dir. I think the two modes is a good solution.

Thanks.
Kind regards,
Roserio

1 Like

I’m a big fan of this approach. Reports rarely should look exactly like an interactive form.

1 Like

Hi @stefano.menci

Thanks for your help. I agree with your line of thought. I am realizing that the best way to think about the PDFRendered is to think of it as a tool that allows to created PDF reports in the background. I was too focused on the GUI form itself, but indeed, eventually the PDF rendering will be a background task triggered by a timer or event. In that sense, the GUI form itself is not relevant but rather the object values like plots, images, label text, … on it. Thus I will follow @jshaffstall suggestion and create a second form that will look more like a report and using only the values from the “GUI” form that are relevant.

Thanks for the example with the dictionary. I will test it with the different types of data I want to use.

Cheers,
Roserio V.

1 Like

Yes, I never generate a PDF using a form designed to be interactive.

What the user sees as a checkbox or a dropdown, will be rendered as text or other form.

I usually pass an id, not the full data to the form. Then the form will use that id to get a row from a data table and get all the data it needs.

1 Like

Thanks for the additional suggestions.
I will take a look on this, it sounds like a distinction between pass by reference or pass by value. I am still getting familiarized with Anvil, so some aspects will become more intuitive with time.