Skip to content

Commit 32a6869

Browse files
authored
Merge pull request #13 from alekswebnet/12-open-a-file-that-i-downloaded-outside-of-the-framework
Add access to PDFViewerApplication, update docs, refactor, fix tests
2 parents 227469a + 5732838 commit 32a6869

File tree

10 files changed

+309
-176
lines changed

10 files changed

+309
-176
lines changed

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ A custom element, based on [PDF.js default viewer](https://mozilla.github.io/pdf
44

55
⚠️ `pdfjs-viewer-element` requires PDF.js [prebuilt](http://mozilla.github.io/pdf.js/getting_started/), that includes the generic build of PDF.js and the viewer. To use the package you should [download](http://mozilla.github.io/pdf.js/getting_started/) and **place the prebuilt** files to some directory of your project. Then specify the path to this directory with `viewer-path` property (`/pdfjs` by default).
66

7+
You have full access to PDF.js viewer application using `initialize` method.
8+
79
## Status
810

911
[![npm version](https://img.shields.io/npm/v/pdfjs-viewer-element?logo=npm&logoColor=fff)](https://www.npmjs.com/package/pdfjs-viewer-element)
@@ -67,5 +69,19 @@ Using browser:
6769

6870
For more clarity, see the [Api docs page](https://alekswebnet.github.io/pdfjs-viewer-element/#api).
6971

72+
## PDF.js Viewer Application
73+
74+
`initialize` - using this method you can access PDFViewerApplication and use methods and events of PDF.js default viewer
75+
76+
```javascript
77+
document.addEventListener('DOMContentLoaded', async () => {
78+
const viewer = document.querySelector('pdfjs-viewer-element')
79+
// Wait for the viewer initialization, receive PDFViewerApplication
80+
const viewerApp = await viewer.initialize()
81+
// Open PDF file data using Uint8Array instead of URL
82+
viewerApp.open(pdfData)
83+
})
84+
```
85+
7086
## License
71-
[MIT](http://opensource.org/licenses/MIT).
87+
[MIT](http://opensource.org/licenses/MIT)

index.html

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</head>
1010
<body style="margin: 0">
1111
<pdfjs-viewer-element
12-
src="/sample-pdf-file.pdf"
12+
src="/fake-file.pdf"
1313
viewer-path="/pdfjs-3.9.179-dist"
1414
style="height: min(500px, 50dvh)">
1515
</pdfjs-viewer-element>
@@ -26,11 +26,38 @@
2626
locale="es"
2727
style="height: min(500px, 50dvh)">
2828
</pdfjs-viewer-element>
29-
<pdfjs-viewer-element
30-
src="/sample-pdf-10MB.pdf"
29+
<pdfjs-viewer-element
30+
id="base-viewer"
3131
viewer-path="/pdfjs-3.9.179-dist"
3232
locale="uk"
3333
style="height: min(500px, 50dvh)">
3434
</pdfjs-viewer-element>
35+
<button>Open file</button>
3536
</body>
37+
38+
<script>
39+
const pdfData = Uint8Array.from(atob(
40+
'JVBERi0xLjcKCjEgMCBvYmogICUgZW50cnkgcG9pbnQKPDwKICAvVHlwZSAvQ2F0YWxvZwog' +
41+
'IC9QYWdlcyAyIDAgUgo+PgplbmRvYmoKCjIgMCBvYmoKPDwKICAvVHlwZSAvUGFnZXMKICAv' +
42+
'TWVkaWFCb3ggWyAwIDAgMjAwIDIwMCBdCiAgL0NvdW50IDEKICAvS2lkcyBbIDMgMCBSIF0K' +
43+
'Pj4KZW5kb2JqCgozIDAgb2JqCjw8CiAgL1R5cGUgL1BhZ2UKICAvUGFyZW50IDIgMCBSCiAg' +
44+
'L1Jlc291cmNlcyA8PAogICAgL0ZvbnQgPDwKICAgICAgL0YxIDQgMCBSIAogICAgPj4KICA+' +
45+
'PgogIC9Db250ZW50cyA1IDAgUgo+PgplbmRvYmoKCjQgMCBvYmoKPDwKICAvVHlwZSAvRm9u' +
46+
'dAogIC9TdWJ0eXBlIC9UeXBlMQogIC9CYXNlRm9udCAvVGltZXMtUm9tYW4KPj4KZW5kb2Jq' +
47+
'Cgo1IDAgb2JqICAlIHBhZ2UgY29udGVudAo8PAogIC9MZW5ndGggNDQKPj4Kc3RyZWFtCkJU' +
48+
'CjcwIDUwIFRECi9GMSAxMiBUZgooSGVsbG8sIHdvcmxkISkgVGoKRVQKZW5kc3RyZWFtCmVu' +
49+
'ZG9iagoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDEwIDAwMDAwIG4g' +
50+
'CjAwMDAwMDAwNzkgMDAwMDAgbiAKMDAwMDAwMDE3MyAwMDAwMCBuIAowMDAwMDAwMzAxIDAw' +
51+
'MDAwIG4gCjAwMDAwMDAzODAgMDAwMDAgbiAKdHJhaWxlcgo8PAogIC9TaXplIDYKICAvUm9v' +
52+
'dCAxIDAgUgo+PgpzdGFydHhyZWYKNDkyCiUlRU9G'), (m) => m.codePointAt(0));
53+
54+
55+
document.addEventListener('DOMContentLoaded', async () => {
56+
const viewer = document.querySelector('#base-viewer')
57+
// Wait for the viewer initialization
58+
const viewerApp = await viewer.initialize()
59+
// Open PDF file data using Uint8Array instead of URL
60+
viewerApp.open(pdfData)
61+
})
62+
</script>
3663
</html>

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "pdfjs-viewer-element",
3-
"version": "2.3.3",
3+
"version": "2.4.0",
44
"license": "MIT",
55
"author": {
66
"name": "Oleksandr Shevchuk",
@@ -43,6 +43,6 @@
4343
"typescript": "^4.6.4",
4444
"vite": "^4.4.9",
4545
"vitest": "^0.34.3",
46-
"webdriverio": "^8.16.4"
46+
"webdriverio": "^8.16.7"
4747
}
4848
}

src/pdfjs-viewer-element.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ export class PdfjsViewerElement extends HTMLElement {
3838
}
3939

4040
attributeChangedCallback() {
41-
this.debouncedRenderIframe()
41+
this.onIframeReady(() => this.mountViewer(this.getIframeSrc()))
4242
}
4343

44-
private debouncedRenderIframe = debounce(async () => {
44+
private onIframeReady = debounce(async (callback: () => void) => {
4545
await elementReady('iframe', this.shadowRoot!)
46-
this.renderViewer(this.getIframeSrc())
46+
callback()
4747
}, 0, { leading: true })
4848

4949
private getIframeSrc() {
@@ -62,7 +62,7 @@ export class PdfjsViewerElement extends HTMLElement {
6262
return ''
6363
}
6464

65-
private renderViewer(src: string) {
65+
private mountViewer(src: string) {
6666
if (!src || !this.iframe) return
6767
this.shadowRoot!.replaceChild(this.iframe.cloneNode(), this.iframe)
6868
this.iframe = this.shadowRoot!.querySelector('iframe') as PdfjsViewerElementIframe
@@ -71,16 +71,24 @@ export class PdfjsViewerElement extends HTMLElement {
7171

7272
private setEventListeners() {
7373
document.addEventListener('webviewerloaded', () => {
74-
if (this.getAttribute('src') !== DEFAULTS.src) this.iframe.contentWindow.PDFViewerApplicationOptions?.set('defaultUrl', '')
75-
this.iframe.contentWindow.PDFViewerApplicationOptions?.set('disablePreferences', true);
76-
this.iframe.contentWindow.PDFViewerApplicationOptions?.set('pdfBugEnabled', true);
77-
this.iframe.contentWindow.PDFViewerApplicationOptions?.set('eventBusDispatchToDOM', true);
74+
if (this.getAttribute('src') !== DEFAULTS.src) this.iframe.contentWindow?.PDFViewerApplicationOptions?.set('defaultUrl', '')
75+
this.iframe.contentWindow?.PDFViewerApplicationOptions?.set('disablePreferences', true);
76+
this.iframe.contentWindow?.PDFViewerApplicationOptions?.set('pdfBugEnabled', true);
77+
this.iframe.contentWindow?.PDFViewerApplicationOptions?.set('eventBusDispatchToDOM', true);
7878
});
7979
}
8080

8181
private getFullPath(path: string) {
8282
return path.startsWith('/') ? `${window.location.origin}${path}` : path
8383
}
84+
85+
public initialize = () => new Promise(async (reslove) => {
86+
await elementReady('iframe', this.shadowRoot!)
87+
this.iframe?.addEventListener('load', async () => {
88+
await this.iframe.contentWindow?.PDFViewerApplication?.initializedPromise
89+
reslove(this.iframe.contentWindow?.PDFViewerApplication)
90+
}, { once: true })
91+
})
8492
}
8593

8694
declare global {
@@ -89,8 +97,17 @@ declare global {
8997
}
9098
}
9199

100+
export interface IPdfjsViewerElement extends HTMLElement {
101+
initialize: () => Promise<PdfjsViewerElementIframeWindow['PDFViewerApplication']>
102+
}
103+
92104
export interface PdfjsViewerElementIframeWindow extends Window {
93-
PDFViewerApplication: any,
105+
PDFViewerApplication: {
106+
initializedPromise: Promise<void>;
107+
initialized: boolean;
108+
open: (data: Uint8Array) => void;
109+
eventBus: Record<string, any>;
110+
},
94111
PDFViewerApplicationOptions: {
95112
set: (name: string, value: string | boolean) => void
96113
}
@@ -100,6 +117,12 @@ export interface PdfjsViewerElementIframe extends HTMLIFrameElement {
100117
contentWindow: PdfjsViewerElementIframeWindow
101118
}
102119

120+
export interface PdfjsViewerLoadedEvent extends Event {
121+
detail: {
122+
source: PdfjsViewerElementIframeWindow
123+
}
124+
}
125+
103126
export default PdfjsViewerElement
104127

105128
if (!window.customElements.get('pdfjs-viewer-element')) {

tests/basic.test.ts

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,58 @@
11
import { describe, expect, it } from 'vitest'
2-
import { getViewerContainer, renderViewer } from './test-utils'
2+
import { getFileData, getViewerElement, mountViewer } from './utils'
33
import '../src/pdfjs-viewer-element'
44

55
describe('Basic render process', async () => {
66
it('should render the PDF file', async () => {
7-
await renderViewer(`
7+
const viewerApp = await mountViewer(`
88
<pdfjs-viewer-element
99
src="/sample-pdf-10MB.pdf"
1010
viewer-path="/pdfjs-3.9.179-dist"
1111
></pdfjs-viewer-element>`
1212
)
13-
expect(getViewerContainer()?.querySelector('#numPages')?.textContent).contain('37')
14-
expect(getViewerContainer()?.querySelector('#viewer .page')).exist
13+
expect(getViewerElement()).exist
14+
15+
viewerApp.eventBus.on('pagesloaded', () => {
16+
expect(getViewerElement('#numPages')?.textContent).contain('37')
17+
})
1518
})
1619

1720
it('should not render PDF with wrong url', async () => {
18-
await renderViewer(`
21+
await mountViewer(`
1922
<pdfjs-viewer-element
2023
src="/fake-file.pdf"
2124
viewer-path="/pdfjs-3.9.179-dist"
2225
></pdfjs-viewer-element>`
2326
)
24-
expect(getViewerContainer()?.querySelector('#viewer .page')).not.exist
27+
expect(getViewerElement()).exist
28+
expect(getViewerElement('#viewer .page')).not.exist
29+
})
30+
31+
it('should not render the viewer with wrong viewer path', async () => {
32+
await mountViewer(`
33+
<pdfjs-viewer-element
34+
src="/sample-pdf-10MB.pdf"
35+
viewer-path="/fake-dist"
36+
></pdfjs-viewer-element>`
37+
)
38+
expect(getViewerElement()).not.exist
2539
})
2640

27-
it('should not render the viewer with wrong viewer-path attribute', async () => {
28-
await renderViewer(`
41+
it('should open the external file with base64 source', async () => {
42+
const viewerApp = await mountViewer(`
2943
<pdfjs-viewer-element
30-
src="/sample-pdf-10MB.pdf"
31-
viewer-path="/fake-dist"
44+
src="/sample-pdf-10MB.pdf"
45+
viewer-path="/pdfjs-3.9.179-dist"
3246
></pdfjs-viewer-element>`
3347
)
34-
expect(getViewerContainer()).not.exist
48+
49+
expect(getViewerElement()).exist
50+
51+
const file = await getFileData()
52+
viewerApp.open(file)
53+
54+
viewerApp.eventBus.on('pagesloaded', () => {
55+
expect(getViewerElement('#viewer .page')).exist
56+
})
3557
})
3658
})

tests/test-utils.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.

tests/utils.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { IPdfjsViewerElement, PdfjsViewerElementIframe } from "../src/pdfjs-viewer-element"
2+
3+
export const getIframe = (): PdfjsViewerElementIframe => {
4+
return document.body.querySelector('pdfjs-viewer-element')?.shadowRoot?.querySelector('iframe') as PdfjsViewerElementIframe
5+
}
6+
7+
export const getViewerElement = (element = '#outerContainer'): HTMLElement | null | undefined => {
8+
return getIframe()?.contentDocument?.querySelector(element)
9+
}
10+
11+
export const mountViewer = async (template: string) => {
12+
document.body.innerHTML = ''
13+
document.body.innerHTML = template
14+
15+
const viewer = document.body.querySelector('pdfjs-viewer-element')
16+
return await (viewer as IPdfjsViewerElement).initialize()
17+
}
18+
19+
export const getFileData = () => {
20+
const baseStr =
21+
'JVBERi0xLjcKCjEgMCBvYmogICUgZW50cnkgcG9pbnQKPDwKICAvVHlwZSAvQ2F0YWxvZwog' +
22+
'IC9QYWdlcyAyIDAgUgo+PgplbmRvYmoKCjIgMCBvYmoKPDwKICAvVHlwZSAvUGFnZXMKICAv' +
23+
'TWVkaWFCb3ggWyAwIDAgMjAwIDIwMCBdCiAgL0NvdW50IDEKICAvS2lkcyBbIDMgMCBSIF0K' +
24+
'Pj4KZW5kb2JqCgozIDAgb2JqCjw8CiAgL1R5cGUgL1BhZ2UKICAvUGFyZW50IDIgMCBSCiAg' +
25+
'L1Jlc291cmNlcyA8PAogICAgL0ZvbnQgPDwKICAgICAgL0YxIDQgMCBSIAogICAgPj4KICA+' +
26+
'PgogIC9Db250ZW50cyA1IDAgUgo+PgplbmRvYmoKCjQgMCBvYmoKPDwKICAvVHlwZSAvRm9u' +
27+
'dAogIC9TdWJ0eXBlIC9UeXBlMQogIC9CYXNlRm9udCAvVGltZXMtUm9tYW4KPj4KZW5kb2Jq' +
28+
'Cgo1IDAgb2JqICAlIHBhZ2UgY29udGVudAo8PAogIC9MZW5ndGggNDQKPj4Kc3RyZWFtCkJU' +
29+
'CjcwIDUwIFRECi9GMSAxMiBUZgooSGVsbG8sIHdvcmxkISkgVGoKRVQKZW5kc3RyZWFtCmVu' +
30+
'ZG9iagoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDEwIDAwMDAwIG4g' +
31+
'CjAwMDAwMDAwNzkgMDAwMDAgbiAKMDAwMDAwMDE3MyAwMDAwMCBuIAowMDAwMDAwMzAxIDAw' +
32+
'MDAwIG4gCjAwMDAwMDAzODAgMDAwMDAgbiAKdHJhaWxlcgo8PAogIC9TaXplIDYKICAvUm9v' +
33+
'dCAxIDAgUgo+PgpzdGFydHhyZWYKNDkyCiUlRU9G'
34+
35+
return fetch(baseStr)
36+
.then(b => b.arrayBuffer())
37+
.then(buff => new Uint8Array(buff))
38+
}

types/pdfjs-viewer-element.d.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,38 @@ export declare class PdfjsViewerElement extends HTMLElement {
44
static get observedAttributes(): string[];
55
connectedCallback(): void;
66
attributeChangedCallback(): void;
7-
private debouncedRenderIframe;
7+
private onIframeReady;
88
private getIframeSrc;
9-
private renderViewer;
9+
private mountViewer;
1010
private setEventListeners;
1111
private getFullPath;
12+
initialize: () => Promise<unknown>;
1213
}
1314
declare global {
1415
interface Window {
15-
'PdfjsViewerElement': typeof PdfjsViewerElement;
16+
PdfjsViewerElement: typeof PdfjsViewerElement;
1617
}
1718
}
19+
export interface IPdfjsViewerElement extends HTMLElement {
20+
initialize: () => Promise<PdfjsViewerElementIframeWindow['PDFViewerApplication']>;
21+
}
1822
export interface PdfjsViewerElementIframeWindow extends Window {
23+
PDFViewerApplication: {
24+
initializedPromise: Promise<void>;
25+
initialized: boolean;
26+
open: (data: Uint8Array) => void;
27+
eventBus: Record<string, any>;
28+
};
1929
PDFViewerApplicationOptions: {
2030
set: (name: string, value: string | boolean) => void;
2131
};
2232
}
2333
export interface PdfjsViewerElementIframe extends HTMLIFrameElement {
2434
contentWindow: PdfjsViewerElementIframeWindow;
2535
}
36+
export interface PdfjsViewerLoadedEvent extends Event {
37+
detail: {
38+
source: PdfjsViewerElementIframeWindow;
39+
};
40+
}
2641
export default PdfjsViewerElement;

vite.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default defineConfig({
1414
test: {
1515
browser: {
1616
enabled: true,
17-
name: 'chrome',
17+
name: 'firefox',
1818
headless: true
1919
},
2020
},

0 commit comments

Comments
 (0)