Adventures in Aurelia: Creating a Custom PDF Viewer
This article was peer-reviewed by Vildan Softic. Thanks to all the peer reviewers at SitePoint for getting SitePoint content to its best!
Processing PDF files in web applications has always been very tricky. If you are lucky, your users just need to download the file. But sometimes, users need more features. I was lucky in the past, but this time, our users need the app to display PDF documents so that they can save metadata related to each page. Previously, people might have used expensive PDF plugins (such as Adobe Reader) to run in the browser to achieve this. However, after some time and experimentation, I found a better way to integrate a PDF viewer in my web application. Today, we will learn how to simplify PDF processing using Aurelia and PDF.js.
Core points
- Use Aurelia and PDF.js to create a custom, efficient PDF viewer with features such as scaling and scrolling to enhance user interaction and performance.
- Implement two-way data binding for attributes such as current page and zoom level in Aurelia, allowing seamless integration and dynamic updates in the application.
- Develop PDF viewer into reusable Aurelia custom elements that allow you to add multiple viewers to your application without conflict.
- Use PDF.js to process PDF rendering, support asynchronous operations and Web Worker to uninstall processing and improve UI response speed.
- Address potential performance issues by considering virtual scrolling and other optimizations, especially in effectively handling large documents.
- Explore the possibility of converting a custom PDF viewer to an Aurelia plugin to make it easy to integrate into other projects and applications.
Overview: Goal
Our goal today is to build a PDF viewer component in Aurelia that allows for two-way data flow between the viewer and our application. We have three main requirements:
- We want users to be able to load documents, scroll and zoom in/out with good performance.
- We want to be able to bidirectionally bind viewer properties (such as the current page and the current zoom level) to properties in the application.
- We want this viewer to be a reusable component; we want to be able to easily place multiple viewers in the application without conflict.
You can find the code for this tutorial in our GitHub repository, as well as a demo of the completed code here.
Introduction PDF.js
PDF.js is a JavaScript library written by the Mozilla Foundation. It loads PDF documents, parses files and related metadata, and renders the page output to the DOM node (usually <canvas></canvas>
element). The default viewer included in the project provides support for embedded PDF viewers in Chrome and Firefox and can be used as standalone pages or resources (embedded in iframes).
This is really cool. The problem here is that the default viewer, while it has many features, is designed as a standalone webpage. This means that while it can be integrated into a web application, it basically has to run inside an iframe sandbox. The default viewer is designed to get configuration input through its query string, but we cannot easily change the configuration after initial loading, nor can we easily get information and events from the viewer. In order to integrate it with the Aurelia web application—including event handling and two-way binding—we need to create an Aurelia custom component.
Note: If you need a review about PDF.js, please check out our tutorial: Custom PDF rendering in JavaScript using Mozilla's PDF.js
Implementation
To achieve our goal, we will create an Aurelia custom element. However, we will not put the default viewer into our components. Instead, we will create our own viewer that connects to the PDF.js core and viewer library so that we can maximize control of our bindable properties and renderings. For our initial proof of concept, we will start with the Aurelia Skeleton application.
Photoscope code
As you can see from the link above, the skeleton application has many files, many of which we don't need. To simplify operations, we prepared a streamlined version of the skeleton and added some content to it:
- A Gulp task for copying our PDF files to the dist folder (Aurelia is used for bundling).
- PDF.js dependency has been added to package.json.
- In the application root directory, index.html and index.css have undergone some initial style settings.
- The empty copy of the file we are going to use has been added.
- The file src/resources/elements/pdf-document.css contains some CSS styles for custom elements.
So let's get the application up and running.
First, make sure gulp and jspm are installed globally:
npm install -g gulp jspm
Then clone the skeleton and enter it:
git clone git@github.com:sitepoint-editors/aurelia-pdfjs.git -b skeleton cd aurelia-pdfjs
Then install the necessary dependencies:
npm install jspm install -y
Finally run gulp watch and navigate to http://localhost:9000. If everything goes according to plan, you should see a welcome message.
More settings
The next thing to do is find a few PDF files and put them in src/documents. Name them one.pdf and two.pdf. To maximize testing of our custom components, it is best that one of the PDF files is very long, such as War and Peace that can be found in the Gutenberg Project.
After putting the PDF file in place, open src/app.html and src/app.js (As agreed, the App component is the root component of the Aurelia application), and replace the code in it with these two files Code: src/app.html and src/app.js. In this tutorial, we will not discuss these files, but there are good comments in the code.
Gulp will automatically detect these changes and you should see our application UI rendering. That's the setting. Now start showing...
Create Aurelia custom element
We want to create a component that can be used directly for any Aurelia view. Since the Aurelia view is just an HTML snippet included in the HTML5 template tag, an example might look like this:
npm install -g gulp jspm
<pdf-document>
tags are an example of custom elements. It and its properties (such as scale and page) are not native properties of HTML, but we can use Aurelia custom elements to create it. Custom elements are easy to create, using Aurelia's basic building blocks: views and ViewModel. Therefore, we will first build our ViewModel, named pdf-document.js, as shown below:
git clone git@github.com:sitepoint-editors/aurelia-pdfjs.git -b skeleton cd aurelia-pdfjs
The main content to note here is the @bindable
decorator; by creating binding properties with configuration defaultBindingMode: bindingMode.twoWay
, and by creating handler methods (urlChanged, pageChanged, etc.) in our ViewModel, we can monitor and respond to changes to related attributes we place on custom elements. This will allow us to control our PDF viewer simply by changing the properties on the element.
We will then create the initial view paired with our ViewModel.
npm install jspm install -y
(The following content is basically consistent with the original text, but some sentences have been adjusted in detail to maintain fluency and readability and avoid duplication.)
Integrated PDF.js
PDF.js is divided into three parts: core library (processing the parsing and interpretation of PDF documents), display library (building available APIs on top of the core layer), and web viewer plug-in (pre-built we mentioned earlier). Web page). For our purposes, we will use the core library through the display API; we will build our own viewer.
Show API exports a library object named PDFJS, which allows us to set some configuration variables and load our document using PDFJS.getDocument(url). The API is completely asynchronous - it sends and receives messages to the Web Worker, so it relies heavily on JavaScript Promise. We will mainly use the PDFDocumentProxy object asynchronously returned from the PDFJS.getDocument() method and the PDFPageProxy object asynchronously returned from the PDFDocumentProxy.getPage().
Although the documentation is a bit sparse, PDF.js has some examples of creating a basic viewer, here and here. We will build our custom components based on this.
Web Worker Integration
PDF.js uses Web Worker to uninstall its rendering tasks. Because of how Web Workers run in a browser environment (they are actually sandboxed), we are forced to load Web Workers using the direct file path to JavaScript files instead of the usual module loaders. Fortunately, Aurelia provides a loader abstraction, so we don't have to reference static file paths (this may change when we bundle the application).
If you are following our version of the repository, you may have installed the pdfjs-dist package, otherwise you will need to do this now (for example, using jspm jspm install npm:pdfjs-dist@^1.5.391). We will then inject Aurelia's loader abstraction using Aurelia's dependency injection module and use the loader to load the Web Worker file in our constructor as follows:
Loading page
PDF.js library handles the loading, parsing and display of PDF documents. It has built-in support for partial downloads and authentication. All we have to do is provide the URI of the relevant document. PDF.js will return a Promise object that resolves to a JavaScript object representing the PDF document and its metadata.
The loading and display of the PDF will be driven by our bindable attribute; in this case it will be the url attribute. Basically, when the URL changes, the custom element should ask PDF.js to issue a request to the file. We will do this in the urlChanged handler and make some changes to our constructor to initialize some properties and some changes to our detached method to clean up.
For each page of the document, we will create a <canvas></canvas>
element in the DOM that is located in a scrollable container with a fixed height. To do this, we will use Aurelia's basic template functionality, using a repeater. Because each PDF page can have its own size and orientation, we will set the width and height of each canvas element according to the PDF page viewport.
Rendering page
Now that we have loaded the pages, we need to be able to render them to the DOM elements. To do this, we will rely on the rendering functionality of PDF.js. The PDF.js viewer library has an asynchronous API dedicated to rendering pages; their website has a great example showing how to create a renderContext object and pass it to the PDF.js render method. We extract this code from the example and wrap it in a render function:
Implement scrolling
To provide a familiar and seamless experience, our components should display the page as parts of a fully scrollable document. We can do this by having our container have a fixed height with scroll overflow, which can be achieved through CSS.
To maximize the performance of large documents, we will do the following things. First, we will use Aurelia's TaskQueue to batch change the DOM. Second, we will track the page that PDF.js has rendered so it doesn't have to redo the work it has done. Finally, we will render the visible page only after the scrolling stops by using Aurelia's debounce binding behavior. Here is how we will run when scrolling:
Achieve scaling
When we scale, we want to update the current zoom level. We will do this in the scaleChanged property handler. Basically, we will resize all the canvas elements to reflect the new viewport size for each page of a given scale. We will then re-render what is displayed in the current viewport and restart the loop.
The final result
Let's review our goals:
- We want users to be able to load documents, scroll and zoom in/out with good performance.
- We want to be able to bidirectionally bind viewer properties (such as the current page and the current zoom level) to properties in the application.
- We want this viewer to be a reusable component; we want to be able to easily place multiple viewers in the application without conflict.
The final code can be found in our GitHub repository, as well as a demo of the completed code here. Although there is still room for improvement, we have reached our goal!
(The following content is basically consistent with the original text, but some sentences have been adjusted in detail to maintain fluency and readability and avoid duplication.)
Post-project analysis and improvement
There is always room for improvement. It is always a good habit to conduct post-project analysis and determine areas that need to be solved in future iterations. Here are some things I want to upgrade in PDF viewer implementation:
Create plugin
Aurelia provides a plug-in system. Converting this proof of concept to an Aurelia plugin will make it an out-of-use resource for any Aurelia application. Aurelia Github repository provides a plugin skeleton project which will be a good starting point. This way, others can use this feature without rebuilding it!
Optimal
Processing PDF files in web applications has always been very tricky. But with the resources available today, we can achieve more than ever by combining libraries and their capabilities. Today, we have seen an example of a basic PDF viewer – a viewer that can be extended with custom features, because we have full control over it. The possibilities are endless! Are you ready to build something? Please let me know in the comments below.
FAQs (FAQs) about Aurelia Custom PDF Viewer Components
(The following content is basically consistent with the original text, but some sentences have been adjusted in detail to maintain fluency and readability and avoid duplication.)
The above is the detailed content of Adventures in Aurelia: Creating a Custom PDF Viewer. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

Frequently Asked Questions and Solutions for Front-end Thermal Paper Ticket Printing In Front-end Development, Ticket Printing is a common requirement. However, many developers are implementing...

JavaScript is the cornerstone of modern web development, and its main functions include event-driven programming, dynamic content generation and asynchronous programming. 1) Event-driven programming allows web pages to change dynamically according to user operations. 2) Dynamic content generation allows page content to be adjusted according to conditions. 3) Asynchronous programming ensures that the user interface is not blocked. JavaScript is widely used in web interaction, single-page application and server-side development, greatly improving the flexibility of user experience and cross-platform development.

There is no absolute salary for Python and JavaScript developers, depending on skills and industry needs. 1. Python may be paid more in data science and machine learning. 2. JavaScript has great demand in front-end and full-stack development, and its salary is also considerable. 3. Influencing factors include experience, geographical location, company size and specific skills.

How to merge array elements with the same ID into one object in JavaScript? When processing data, we often encounter the need to have the same ID...

Learning JavaScript is not difficult, but it is challenging. 1) Understand basic concepts such as variables, data types, functions, etc. 2) Master asynchronous programming and implement it through event loops. 3) Use DOM operations and Promise to handle asynchronous requests. 4) Avoid common mistakes and use debugging techniques. 5) Optimize performance and follow best practices.

Discussion on the realization of parallax scrolling and element animation effects in this article will explore how to achieve similar to Shiseido official website (https://www.shiseido.co.jp/sb/wonderland/)...

The latest trends in JavaScript include the rise of TypeScript, the popularity of modern frameworks and libraries, and the application of WebAssembly. Future prospects cover more powerful type systems, the development of server-side JavaScript, the expansion of artificial intelligence and machine learning, and the potential of IoT and edge computing.

In-depth discussion of the root causes of the difference in console.log output. This article will analyze the differences in the output results of console.log function in a piece of code and explain the reasons behind it. �...
