# Adding PDF generate feature in Phoenix LiveView app

Generating pdf is very common in day-to-day web applications. In E-commerce websites like Amazon when we do any shopping we get the option to get the invoice. It's not an easy task to implement it people often have a hard time implementing it because of different dependencies issues. You can implement it easily with Phoenix LiveView because of the awesome Elixir ecosystem. In this blog, I am going to explain every step that is required to implement the feature to generate and download `PDF`. So, let's get started.

### Creating a Phoenix LiveView Project

First of all, we should have a LiveView project. If you have one skip this section and continue `Installing Chromic PDF` section. If you don't have one then create a new LiveView project by running `mix phx.new my_app --live`. We will then navigate to the project directory by running cd `my_app` in the terminal. And finally, run the project running `mix phx.server` in the terminal. Once, the project starts running navigate to localhost:4000 to see the "Welcome phoenix" page.

### Installing Chromic PDF

To create a PDF, the Elixir community has an awesome library called [chromic\_pdf](https://github.com/bitcrowd/chromic_pdf). It is very easy to integrate. It does not use `puppeteer`, and hence does not require Node.js. It communicates directly with Chrome's DevTools API over pipes, offering the same performance as `puppeteer`, if not better. So we are going to use it. Let's add it to our project. Open the `mix.exs` file and make an entry of it as shown below.

```elixir
defp deps do
  ...
  {:chromic_pdf, "~> 1.14"},
  ...
end
```

Then install the dependency by running `mix deps.get`. It will install all the dependencies. Now, we will run `ChromicPDF` as part of our application by adding it in `application.ex`.

```elixir
defmodule MyApp.Application do
   ...
  def start(_type, _args) do
    children = [
      ...
      ChromicPDF
      ...
    ]
    ...
  end
...
end
```

This will make sure that this process should be started when the application starts and is supervised by the Supervisor.

### Adding endpoint to generate PDF

We want our pdf generator to be served at a different endpoint and serve a regular HTTP response because we cannot send `pdf` over a web socket i.e. LiveView. We will add a `GET` endpoint `/generate-invoice` under `BillsController`.

```javascript
scope "/", MyAppWeb do
    pipe_through [:browser, :require_authenticated_distributor]

    get "/generate-invoice", BillsController, :index
end
```

Make sure to add the `require_authenticated_distributor` plug (or any other authentication/authorization plug) in the pipeline to make sure the endpoint is authenticated else it will be exposed to the attackers.

### Adding Controller

Now, we will add the controller for the endpoint we added in the router

```elixir
defmodule MyAppWeb.BillsController do
  use MyAppWeb, :controller

  def index(conn, _params) do
    #Assign some attributes and values which we can use in the pdf.
    conn = assign(conn, :total_amount_paid, 10000) 
    {:ok, pdf} = to_pdf(conn.assigns) ### We will write this function later.

    send_download(
        conn,
        {:binary, Base.decode64!(pdf)},
        content_type: "application/pdf",
        filename: "invoice"
    )
  end
end
```

At the end of the `index` action, we can see the `send_download` button which takes 4 parameters:

* `conn:` a connection struct.
    
* `content` in binary tuple format i.e.`pdf` in this case.
    
* `content_type`: the content type.
    
* `filename`: give the file name of our choice.
    

`send_download` is used to send the given file or binary as a download. In other words, it will be the reason to download the generated PDF in your browser.

### Implementing `to_pdf` function

In the `index` action, we saw the `to_pdf` function in the previous section. We are going to implement it in this section. In this function, we will see `ChromicPDF` in action. You will see two functions `source_and_options` and `print_to_pdf`.

* `source_and_options:` The module `Template` in `ChromicPDf` usage is mainly to generate HTML from the provided template and styling. We can see it calls the `source_and_options` function which returns the source and options for a PDF to be printed, as per the given set of template options like `content` and `size` of the PDF.
    
* `print_to_pdf:` As the name suggests it Prints a PDF. It is a blocking call which means it will block further code until the PDF is generated.
    

Refer to the following code

```javascript
def to_pdf(assigns) do
    [
      content: content(assigns), # `content` function yet to be implemented
      size: :a4,
    ]
    |> ChromicPDF.Template.source_and_options()
    |> ChromicPDF.print_to_pdf()
end
```

### Generating content for the PDF

Now, the most important part of the implementation. You have seen the `content` function in `to_pdf` which is yet to be implemented. We are going to implement that and learn why we are going to use `components` and not `views`. Earlier, `Phoenix.View` was used to render HTML (view) which was later passed to the `ChromicPDF#source_and_options` function. But since `Phoenix 1.7` the `Phoenix.View` has been eliminated and substituted with the new `Phoenix.Template`. Phoenix 1.7 supports function components to render both controller-based and LiveView-based components. So, we are going to use `Phoenix.LiveComponent` instead of `Phoenix.View`. For this, we will create a component `PdfRenderer` component inside the `components` folder, and in the module, we will add a `render` function. In this function we will write the HTML and CSS we want to render for the PDF. Refer to the following code.

```elixir
defmodule PdfRendererComponent do
  use Phoenix.LiveComponent

  def render(assigns) do
    ~H"""
      <h1>Invoice</h1>
      <p style="font-size: 200%;">Total:  <%= @total_amount_paid %> </p>
    """
  end
end
```

In the above `render` function you can see the field `total_amount_paid` attribute. Where does this come from? If you remember in the controller we assigned the `total_amount_paid` attribute to the `conn` struct. We are getting it here because of the `assign` parameter in the `render` function. To access it in this component we have to pass the `conn.assign` struct directly to the component. Something like this,

```javascript
PdfRendererComponent.render(assigns)
```

We will implement this function `content` to render this component. Refer to the following code.

```elixir
defp content(assigns) do
    Phoenix.HTML.Safe.to_iodata(PdfRendererComponent.render(assigns))
end
```

In Phoenix, you can call the functions of the components using `Phoenix.HTML.Safe.to_iodata`. The official documentation says **"(It) provides better performance when sending or streaming data to the client"**. So, in this case, the HTML is sent to the `ChromicPDF.Template.source_and_options()` to render. Whatever HTML and CSS we write in the render function will be passed to the `source_and_options`. The final working code of the controller is below.

```elixir
defmodule MyAppWeb.BillsController do
  use MyAppWeb, :controller

  def index(conn, _params) do
    #Assign some attributes and values which we can use in the pdf.
    conn = assign(conn, :total_amount_paid, 10000) 
    {:ok, pdf} = to_pdf(conn.assigns) ### We will write this function later.

    send_download(
        conn,
        {:binary, Base.decode64!(pdf)},
        content_type: "application/pdf",
        filename: "invoice"
    )
  end

  def to_pdf(assigns) do
    [
      content: content(assigns), 
      size: :a4,
    ]
    |> ChromicPDF.Template.source_and_options()
    |> ChromicPDF.print_to_pdf()
 end

 defp content(assigns) do
    Phoenix.HTML.Safe.to_iodata(PdfRendererComponent.render(assigns))
 end
end
```

### Adding Download button

Finally, we need to add an `Invoice/Download` button to call this endpoint we created. It will be a simple `link` tag.

```javascript
<%= link "Invoice", to: "/generate-invoice", class: "button", download: 'invoice.pdf` %>
```

You’ll notice I have added an `download` attribute to the link tag. I wrote a small [TIL](https://abulasar.com/til-add-the-download-attribute-on-the-link-tag-when-calling-the-download-endpoint) blog on its usage you can check it later 😊.

I hope you like this blog. If you have any questions please comment below. Thanks for reading 😊.

### References

* [Chromic PDF](https://github.com/bitcrowd/chromic_pdf)
