Dynamically changing PDF files
Vue PDF Viewer allows developers to change PDF files in a Vue application dynamically. There are 3 ways to load PDF files into the PDF Viewer:
- URL (http/https)
- Internal File Path
- Blob (From API or File Upload)
This tutorial will guide you on how to switch between different PDF sources using radio buttons. We'll create the following components in the components
directory:
AppPdfViewer.vue
- A reusable PDF viewer componentPdfViewerUrl.vue
- A component to display PDF from a URLPdfViewerFile.vue
- A component to display PDF from a file pathPdfViewerBlob.vue
- A component to display PDF from blob dataRadioGroupPdfSource.vue
- A component to switch between PDF sources
Create a reusable AppPdfViewer
component
Refer to Making Vue PDF Viewer Reusable for more information.
After creating the AppPdfViewer
component, then you can use it in your application by passing different PDF file sources to it. The AppPdfViewer
component will display the PDF file based on the source type.
Display a default PDF file via URL
Here is an example of how you can load a PDF file from URL (http/https) with the AppPdfViewer
component.
Let's create a PdfViewerUrl
component to display a PDF file from URL (http/https).
<script setup lang="ts">
import { ref } from "vue";
import AppPdfViewer from "./AppPdfViewer.vue";
const DEFAULT_FILE = "https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf";
const pdfFile = ref<string>(DEFAULT_FILE);
</script>
<template>
<p>PDF File: {{ pdfFile }}</p>
<div :style="{ width: '1028px', height: '700px' }">
<AppPdfViewer :src="pdfFile" />
</div>
</template>
<script setup>
import { ref } from "vue";
import AppPdfViewer from "./AppPdfViewer.vue";
const DEFAULT_FILE = "https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf";
const pdfFile = ref(DEFAULT_FILE);
</script>
<template>
<p>PDF File: {{ pdfFile }}</p>
<div :style="{ width: '1028px', height: '700px' }">
<AppPdfViewer :src="pdfFile" />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import AppPdfViewer from "./AppPdfViewer.vue";
const DEFAULT_FILE = "https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf";
export default defineComponent({
data() {
return {
pdfFile: DEFAULT_FILE,
};
}
});
</script>
<template>
<p>PDF File: {{ pdfFile }}</p>
<div :style="{ width: '1028px', height: '700px' }">
<AppPdfViewer :src="pdfFile" />
</div>
</template>
<script>
import AppPdfViewer from "./AppPdfViewer.vue";
const DEFAULT_FILE = "https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf";
export default {
data() {
return {
pdfFile: DEFAULT_FILE,
};
}
}
</script>
<template>
<p>PDF File: {{ pdfFile }}</p>
<div :style="{ width: '1028px', height: '700px' }">
<AppPdfViewer :src="pdfFile" />
</div>
</template>
Display a default PDF file via Internal File Path
Another method to load a PDF file is via an Internal File Path. With Vite, there are 2 ways to do this:
- Place the PDF files in the
public
directory if they do not need processing by Vite. Files in the public directory are served as-is. - Use the
src/assets
directory for PDFs that need to be processed or optimized by Vite.
Here is an example of how you can load a PDF file from an Internal File Path from the public
directory with the AppPdfViewer
component.
Let's create a PdfViewerForm
component to display a PDF file from an Internal File Path from the public
directory.
<script setup lang="ts">
import { ref } from "vue";
import AppPdfViewer from "./AppPdfViewer.vue";
const DEFAULT_FILE = "/Sample-PDF-Form.pdf"; // The file is in the public directory
const pdfFile = ref<string>(DEFAULT_FILE);
</script>
<template>
<p>PDF File: {{ pdfFile }}</p>
<div :style="{ width: '1028px', height: '700px' }">
<AppPdfViewer :src="pdfFile" />
</div>
</template>
<script setup>
import { ref } from "vue";
import AppPdfViewer from "./AppPdfViewer.vue";
const DEFAULT_FILE = "/Sample-PDF-Form.pdf"; // The file is in the public directory
const pdfFile = ref(DEFAULT_FILE);
</script>
<template>
<p>PDF File: {{ pdfFile }}</p>
<div :style="{ width: '1028px', height: '700px' }">
<AppPdfViewer :src="pdfFile" />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import AppPdfViewer from "./AppPdfViewer.vue";
export default defineComponent({
data() {
return {
pdfSrc: "/Sample-PDF-Form.pdf",
};
}
});
</script>
<template>
<p>PDF File: {{ pdfFile }}</p>
<div :style="{ width: '1028px', height: '700px' }">
<AppPdfViewer :src="pdfFile" />
</div>
</template>
<script setup>
import AppPdfViewer from "./AppPdfViewer.vue";
export default {
data() {
return {
pdfFile: "/Sample-PDF-Form.pdf",
}
}
}
</script>
<template>
<p>PDF File: {{ pdfFile }}</p>
<div :style="{ width: '1028px', height: '700px' }">
<AppPdfViewer :src="pdfFile" />
</div>
</template>
Display a default PDF file via Blob
This method is to load a PDF file by converting it into a Blob URL, which is useful when working with files from APIs or file uploads.
Let's create a PdfViewerBlob
component to display a PDF file from Blob.
<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref } from "vue";
import AppPdfViewer from "./AppPdfViewer.vue";
const pdfFile = ref<string>("");
onMounted(async () => {
// Fetch PDF from an API (able to use Internal File Path or URL (http/https)
const response = await fetch(
"/Monterail - State_of_vue.js_2021_report_short_annotated.pdf"
);
const blob = await response.blob();
pdfFile.value = URL.createObjectURL(blob);
});
onBeforeUnmount(() => {
// Clean up the Blob URL to avoid memory leaks
if (pdfFile.value) {
URL.revokeObjectURL(pdfFile.value);
}
});
</script>
<template>
<p>PDF File: {{ pdfFile }}</p>
<div :style="{ width: '1028px', height: '700px' }">
<AppPdfViewer :src="pdfFile" />
</div>
</template>
<script setup>
import { ref } from "vue";
import AppPdfViewer from "./AppPdfViewer.vue";
const pdfFile = ref("");
onMounted(async () => {
// Fetch PDF from an API (able to use Internal File Path or URL (http/https)
const response = await fetch(
"/Monterail - State_of_vue.js_2021_report_short_annotated.pdf"
);
const blob = await response.blob();
pdfFile.value = URL.createObjectURL(blob);
});
onBeforeUnmount(() => {
// Clean up the Blob URL to avoid memory leaks
if (pdfFile.value) {
URL.revokeObjectURL(pdfFile.value);
}
});
</script>
<template>
<p>PDF File: {{ pdfFile }}</p>
<div :style="{ width: '1028px', height: '700px' }">
<AppPdfViewer :src="pdfFile" />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import AppPdfViewer from "./AppPdfViewer.vue";
export default defineComponent({
data() {
return {
pdfFile: "" as string,
};
},
async mounted() {
// Fetch PDF from an API (able to use Internal File Path or URL (http/https)
const response = await fetch(
"/Monterail - State_of_vue.js_2021_report_short_annotated.pdf",
);
const blob = await response.blob();
this.pdfFile = URL.createObjectURL(blob);
},
beforeUnmount() {
// Clean up the Blob URL to avoid memory leaks
if (this.pdfFile) {
URL.revokeObjectURL(this.pdfFile);
}
},
});
</script>
<template>
<p>PDF File: {{ pdfFile }}</p>
<div :style="{ width: '1028px', height: '700px' }">
<AppPdfViewer :src="pdfFile" />
</div>
</template>
<script>
import AppPdfViewer from "./AppPdfViewer.vue";
export default {
data() {
return {
pdfFile: "",
};
},
async mounted() {
// Fetch PDF from an API (able to use Internal File Path or URL (http/https)
const response = await fetch(
"/Monterail - State_of_vue.js_2021_report_short_annotated.pdf"
);
const blob = await response.blob()
this.pdfFile = URL.createObjectURL(blob)
},
beforeUnmount() {
// Clean up the Blob URL to avoid memory leaks
if (this.pdfFile) {
URL.revokeObjectURL(this.pdfFile)
}
},
});
</script>
<template>
<p>PDF File: {{ pdfFile }}</p>
<div :style="{ width: '1028px', height: '700px' }">
<AppPdfViewer :src="pdfFile" />
</div>
</template>
onBeforeUnmount
ensures the Blob URL is revoked to prevent memory leaks.
Remark: For more details of the best practices of each method, you may refer to Handling Source of PDF File.
Create a radio buttons component to switch between PDF sources
Here is an example of how you can create a radio buttons component to switch between PDF sources. Let's create a RadioGroupPdfSource
component to switch between PDF sources.
<script setup lang="ts">
import { ref, reactive, watch } from "vue";
type SourceType = "general" | "form" | "annotation";
type SourceOption = {
label: string;
value: SourceType;
}
const emit = defineEmits<{
(e: "update:source-type", value: SourceType): void
}>();
const currentSourceType = ref<SourceType>("general");
const fileOptions = reactive<SourceOption[]>([
{
label: "General (URL)",
value: "general",
},
{
label: "PDF Form (Internal File Path)",
value: "form",
},
{
label: "Annotated PDF (Blob)",
value: "annotation",
},
]);
// Emit the source type to the parent component
watch(currentSourceType, (newVal) => {
emit("update:source-type", newVal);
});
</script>
<template>
<div :style="{ padding: '12px' }">
<p>Switch PDF File</p>
<div v-for="option in fileOptions" :key="option.value">
<input type="radio" :id="`src-type-${option.value}`" :value="option.value" v-model="currentSourceType" />
<label :for="`src-type-${option.value}`"> {{ option.label }} </label>
</div>
</div>
</template>
<script setup>
import { ref, reactive, watch } from "vue";
const currentSourceType = ref("general");
const fileOptions = reactive([
{
label: "General (URL)",
value: "general",
},
{
label: "PDF Form (Internal File Path)",
value: "form",
},
{
label: "Annotated PDF (Blob)",
value: "annotation",
},
]);
// Emit the source type to the parent component
watch(currentSourceType, (newVal) => {
emit("update:source-type", newVal);
});
</script>
<template>
<div :style="{ padding: '12px' }">
<p>Switch PDF File</p>
<div v-for="option in fileOptions" :key="option.value">
<input type="radio" :id="`src-type-${option.value}`" :value="option.value" v-model="currentSourceType" />
<label :for="`src-type-${option.value}`"> {{ option.label }} </label>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
type SourceType = "general" | "form" | "annotation";
type SourceOption = {
label: string;
value: SourceType;
}
export default defineComponent({
data() {
return {
currentSourceType: "general" as SourceType,
fileOptions: [
{
label: "General (URL)",
value: "general",
},
{
label: "PDF Form (Internal File Path)",
value: "form",
},
{
label: "Annotated PDF (Blob)",
value: "annotation",
},
] as SourceOption[],
};
},
watch: {
currentSourceType(newVal) {
this.$emit("update:source-type", newVal);
}
}
})
</script>
<template>
<div :style="{ padding: '12px' }">
<p>Switch PDF File</p>
<div v-for="option in fileOptions" :key="option.value">
<input type="radio" :id="`src-type-${option.value}`" :value="option.value" v-model="currentSourceType" />
<label :for="`src-type-${option.value}`"> {{ option.label }} </label>
</div>
</div>
</template>
<script>
export default {
data() {
return {
currentSourceType: "general",
fileOptions: [
{
label: "General (URL)",
value: "general",
},
{
label: "PDF Form (Internal File Path)",
value: "form",
},
{
label: "Annotated PDF (Blob)",
value: "annotation",
},
],
}
},
watch: {
currentSourceType(newVal) {
this.$emit("update:source-type", newVal);
}
}
}
</script>
<template>
<div :style="{ padding: '12px' }">
<p>Switch PDF File</p>
<div v-for="option in fileOptions" :key="option.value">
<input type="radio" :id="`src-type-${option.value}`" :value="option.value" v-model="currentSourceType" />
<label :for="`src-type-${option.value}`"> {{ option.label }} </label>
</div>
</div>
</template>
Complete example
Now, we have five components ready to complete the example.
AppPdfViewer
- The reusable component that displays a PDF file.PdfViewerUrl
- The component that displaysAppPdfViewer
with a PDF file from a URL.PdfViewerForm
- The component that displaysAppPdfViewer
with a PDF file from an Internal File Path.PdfViewerBlob
- The component that displaysAppPdfViewer
with a PDF file from a Blob.RadioGroupPdfSource
- The component that displays radio buttons to switch between different PDF sources.
Let's update the App.vue
file which is the root component of Vue application. It will display the RadioGroupPdfSource
component and conditionally render the appropriate PDF viewer component based on the selected source type.
Here is a complete example of how you can switch between different PDF sources using radio buttons.
<script setup lang="ts">
import { ref } from "vue";
import PdfViewerUrl from "./components/PdfViewerUrl.vue";
import PdfViewerForm from "./components/PdfViewerForm.vue";
import PdfViewerBlob from "./components/PdfViewerBlob.vue";
import RadioGroupPdfSource from "./components/RadioGroupPdfSource.vue";
// The type of the source type
// Should be the same as the type in the `RadioGroupPdfSource` component
type SourceType = "general" | "form" | "annotation";
const currentSourceType = ref<SourceType>("general");
const handleSourceTypeChange = (sourceType: SourceType) => {
currentSourceType.value = sourceType;
};
</script>
<template>
<div>
<RadioGroupPdfSource @update:source-type="handleSourceTypeChange" />
<PdfViewerUrl v-if="currentSourceType === 'general'" />
<PdfViewerForm v-else-if="currentSourceType === 'form'" />
<PdfViewerBlob v-else-if="currentSourceType === 'annotation'" />
</div>
</template>
<script setup>
import { ref } from "vue";
import PdfViewerUrl from "./components/PdfViewerUrl.vue";
import PdfViewerForm from "./components/PdfViewerForm.vue";
import PdfViewerBlob from "./components/PdfViewerBlob.vue";
import RadioGroupPdfSource from "./components/RadioGroupPdfSource.vue";
const currentSourceType = ref("general");
const handleSourceTypeChange = (sourceType) => {
currentSourceType.value = sourceType;
};
</script>
<template>
<div>
<RadioGroupPdfSource @update:source-type="handleSourceTypeChange" />
<PdfViewerUrl v-if="currentSourceType === 'general'" />
<PdfViewerForm v-else-if="currentSourceType === 'form'" />
<PdfViewerBlob v-else-if="currentSourceType === 'annotation'" />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import PdfViewerUrl from "./components/PdfViewerUrl.vue";
import PdfViewerForm from "./components/PdfViewerForm.vue";
import PdfViewerBlob from "./components/PdfViewerBlob.vue";
import RadioGroupPdfSource from "./components/RadioGroupPdfSource.vue";
// The type of the source type
// Should be the same as the type in the `RadioGroupPdfSource` component
type SourceType = "general" | "form" | "annotation";
export default defineComponent({
data() {
return {
currentSourceType: "general" as SourceType,
};
},
methods: {
handleSourceTypeChange(sourceType: SourceType) {
this.currentSourceType = sourceType;
}
}
});
</script>
<template>
<div>
<RadioGroupPdfSource @update:source-type="handleSourceTypeChange" />
<PdfViewerUrl v-if="currentSourceType === 'general'" />
<PdfViewerForm v-else-if="currentSourceType === 'form'" />
<PdfViewerBlob v-else-if="currentSourceType === 'annotation'" />
</div>
</template>
<script>
import PdfViewerUrl from "./components/PdfViewerUrl.vue";
import PdfViewerForm from "./components/PdfViewerForm.vue";
import PdfViewerBlob from "./components/PdfViewerBlob.vue";
import RadioGroupPdfSource from "./components/RadioGroupPdfSource.vue";
export default {
data() {
return {
currentSourceType: "general",
};
},
methods: {
handleSourceTypeChange(sourceType) {
this.currentSourceType = sourceType;
}
}
}
</script>
<template>
<div>
<RadioGroupPdfSource @update:source-type="handleSourceTypeChange" />
<PdfViewerUrl v-if="currentSourceType === 'general'" />
<PdfViewerForm v-else-if="currentSourceType === 'form'" />
<PdfViewerBlob v-else-if="currentSourceType === 'annotation'" />
</div>
</template>
Bonus: Handling error when loading a PDF file
When loading a PDF file, there are some cases that the file might not be found or cannot be loaded. In that case, you can use the loadError
event to handle the error.
Here is steps of how you can handle the loadError
event to display an error message when the PDF file fails to load:
- First,
emit
theloadError
event from theAppPdfViewer
component - Handle the
loadError
event inPdfViewerUrl
,PdfViewerForm
, andPdfViewerBlob
component - Display the error message to the user if there is an error
Here is an example of how you can handle the loadError
event within the PdfViewerUrl
component.
<script setup lang="ts">
import { ref } from "vue";
import AppPdfViewer from "./components/AppPdfViewer.vue";
const DEFAULT_FILE = "https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf";
const pdfFile = ref<string>(DEFAULT_FILE);
const handleLoadError = (error: any) => {
console.error("Error loading PDF file:", error);
alert("Error loading PDF file");
};
</script>
<template>
<p>PDF File: {{ pdfFile }}</p>
<div :style="{ width: '1028px', height: '700px' }">
<AppPdfViewer :src="pdfFile" @loadError="handleLoadError" />
</div>
</template>
<script setup>
import { ref } from "vue";
import AppPdfViewer from "./components/AppPdfViewer.vue";
const DEFAULT_FILE = "https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf";
const pdfFile = ref("");
const handleLoadError = (error) => {
console.error("Error loading PDF file:", error);
alert("Error loading PDF file");
};
</script>
<template>
<p>PDF File: {{ pdfFile }}</p>
<div :style="{ width: '1028px', height: '700px' }">
<AppPdfViewer :src="pdfFile" @loadError="handleLoadError" />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import AppPdfViewer from "./components/AppPdfViewer.vue";
const DEFAULT_FILE = "https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf";
export default defineComponent({
data() {
return {
pdfFile: DEFAULT_FILE,
};
},
methods: {
handleLoadError(error: any) {
console.error("Error loading PDF file:", error);
alert("Error loading PDF file");
}
}
});
</script>
<template>
<p>PDF File: {{ pdfFile }}</p>
<div :style="{ width: '1028px', height: '700px' }">
<AppPdfViewer :src="pdfFile" @loadError="handleLoadError" />
</div>
</template>
<script>
import AppPdfViewer from "./components/AppPdfViewer.vue";
const DEFAULT_FILE = "https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf";
export default {
data() {
return {
pdfFile: DEFAULT_FILE,
};
},
methods: {
handleLoadError(error: any) {
console.error("Error loading PDF file:", error);
alert("Error loading PDF file");
}
}
};
</script>
<template>
<p>PDF File: {{ pdfFile }}</p>
<div :style="{ width: '1028px', height: '700px' }">
<AppPdfViewer :src="pdfFile" @loadError="handleLoadError" />
</div>
</template>
Subsequently, you can apply this concept of handling the loadError
event in both PdfViewerForm
and PdfViewerBlob
components to display an error message accordingly.