Custom Toolbar in Vue PDF Viewer

28 Feb 202624 minutes to read

The PDF Viewer provides APIs for the user interaction options available in its built-in toolbar. Using these APIs, a custom application-level toolbar can be created by hiding the default toolbar and adding Syncfusion EJ2 toolbar components.

Follow these steps to create a custom toolbar for the PDF Viewer:

Step 1: Create a simple PDF Viewer sample.

Create a simple PDF Viewer sample by following the getting started guide.

Step 2: Add template markup for the custom toolbars.

Add toolbar components and the PDF Viewer component to your Vue template. One toolbar handles primary actions, and another handles magnification actions.

<template>
  <div>
    <ejs-toolbar id="customToolbar" ref="toolbar">
      <e-items>
        <e-item prefixIcon='e-pv-open-document-icon' tooltipText='Open' :click="openClicked"></e-item>
        <e-item prefixIcon='e-pv-previous-page-navigation-icon' id="previousPage" tooltipText='Previous Page' align='Center' :click="previousClicked"></e-item>
        <e-item prefixIcon='e-pv-next-page-navigation-icon' id="nextPage" tooltipText='Next Page' align='Center' :click="nextClicked"></e-item>
        <e-item :template='pageNoTemplate' tooltipText='Page Number' align='Center'></e-item>
        <e-item :template='pageTextTemplate' tooltipText='Page Number' align='Center'></e-item>
        <e-item prefixIcon='e-pv-print-document-icon' tooltipText='Print' align='Right' :click="printClicked"></e-item>
        <e-item prefixIcon='e-pv-download-document-icon' tooltipText='Download' align='Right' :click="downloadClicked"></e-item>
      </e-items>
    </ejs-toolbar>

    <ejs-toolbar id="magnificationToolbar">
      <e-items>
        <e-item prefixIcon='e-pv-fit-page-icon' id="fitPage" tooltipText='Fit to page' :click="pageFitClicked"></e-item>
        <e-item prefixIcon='e-pv-zoom-in-icon' id="zoomIn" tooltipText='Zoom in' :click="zoomInClicked"></e-item>
        <e-item prefixIcon='e-pv-zoom-out-icon' id="zoomOut" tooltipText='Zoom out' :click="zoomOutClicked"></e-item>
      </e-items>
    </ejs-toolbar>

    <input type ="file" id="fileUpload" accept=".pdf" style="display:block;visibility:hidden;width:0;height:0;"></input>

    <div class="control-section">
      <ejs-pdfviewer
        id="pdfviewer"
        ref="pdfviewer"
        :pageChange="pageChange"
        :documentLoad="documentLoad"
        :documentPath="documentPath"
        :enableToolbar="enableToolbar">
      </ejs-pdfviewer>
    </div>
  </div>
</template>
<template>
  <div>
    <ejs-toolbar id="customToolbar" ref="toolbar">
      <e-items>
        <e-item prefixIcon='e-pv-open-document-icon' tooltipText='Open' :click="openClicked"></e-item>
        <e-item prefixIcon='e-pv-previous-page-navigation-icon' id="previousPage" tooltipText='Previous Page' align='Center' :click="previousClicked"></e-item>
        <e-item prefixIcon='e-pv-next-page-navigation-icon' id="nextPage" tooltipText='Next Page' align='Center' :click="nextClicked"></e-item>
        <e-item :template='pageNoTemplate' tooltipText='Page Number' align='Center'></e-item>
        <e-item :template='pageTextTemplate' tooltipText='Page Number' align='Center'></e-item>
        <e-item prefixIcon='e-pv-print-document-icon' tooltipText='Print' align='Right' :click="printClicked"></e-item>
        <e-item prefixIcon='e-pv-download-document-icon' tooltipText='Download' align='Right' :click="downloadClicked"></e-item>
      </e-items>
    </ejs-toolbar>

    <ejs-toolbar id="magnificationToolbar">
      <e-items>
        <e-item prefixIcon='e-pv-fit-page-icon' id="fitPage" tooltipText='Fit to page' :click="pageFitClicked"></e-item>
        <e-item prefixIcon='e-pv-zoom-in-icon' id="zoomIn" tooltipText='Zoom in' :click="zoomInClicked"></e-item>
        <e-item prefixIcon='e-pv-zoom-out-icon' id="zoomOut" tooltipText='Zoom out' :click="zoomOutClicked"></e-item>
      </e-items>
    </ejs-toolbar>

    <input type ="file" id="fileUpload" accept=".pdf" style="display:block;visibility:hidden;width:0;height:0;">

    <div class="control-section">
      <ejs-pdfviewer
        id="pdfviewer"
        ref="pdfviewer"
        :pageChange="pageChange"
        :documentLoad="documentLoad"
        :serviceUrl="serviceUrl"
        :documentPath="documentPath"
        :enableToolbar="enableToolbar">
      </ejs-pdfviewer>
    </div>
  </div>
</template>

Step 3: Import and register modules.

import Vue from "vue";
import { PdfViewerPlugin, Toolbar, Magnification, Navigation,
         LinkAnnotation, BookmarkView, Annotation, ThumbnailView,
         Print, TextSelection, TextSearch, FormFields, FormDesigner } from "@syncfusion/ej2-vue-pdfviewer";
import { ToolbarPlugin } from "@syncfusion/ej2-vue-navigations";
import { CalendarPlugin } from '@syncfusion/ej2-vue-calendars';
Vue.use(CalendarPlugin);
Vue.use(PdfViewerPlugin);
Vue.use(ToolbarPlugin);

Step 4: Hide the built-in toolbar.

Use enableToolbar to hide the built-in toolbar and rely on the custom toolbar.

export default Vue.extend({
  data: function () {
    return {
      documentPath: "https://cdn.syncfusion.com/content/pdf/hive-succinctly.pdf",
      enableToolbar: false,
      pageNoTemplate: '<div class=""><input type="text" class="e-input-group e-pv-current-page-number" id="currentPage" /></div>',
      pageTextTemplate: '<div class=""><span class="e-pv-total-page-number" id="totalPage">of 0</span></div>'
    }
  }
});
export default Vue.extend({
  data: function () {
    return {
      serviceUrl: "https://document.syncfusion.com/web-services/pdf-viewer/api/pdfviewer",
      documentPath: "https://cdn.syncfusion.com/content/pdf/hive-succinctly.pdf",
      enableToolbar: false,
      pageNoTemplate: '<div class=""><input type="text" class="e-input-group e-pv-current-page-number" id="currentPage" /></div>',
      pageTextTemplate: '<div class=""><span class="e-pv-total-page-number" id="totalPage">of 0</span></div>'
    }
  }
});

Step 5: Styles for the custom toolbars.

<style>
  #pdfviewer { height: 640px; }
  .control-section { padding-top: 0px !important; }
  #magnificationToolbar {
    background: transparent;
    height: auto;
    min-height: 56px;
    width: auto;
    border: none;
    position: absolute;
    z-index: 900;
    top: calc(100% - 110px);
    left: calc(100% - 120px);
    transform: rotate(90deg);
  }
  div#magnificationToolbar.e-toolbar .e-toolbar-items { background: transparent; padding: 2px 3px 2px 2px; }
  #magnificationToolbar.e-toolbar .e-tbar-btn { border-radius: 50%; min-height: 30px; min-width: 30px; border: 1px solid #c8c8c8; }
  #customToolbar { top: 0px; z-index: 900; }
  .e-bookmark-popup { height: 300px; max-width: 300px; }
  .e-text-search-popup { height: 104px; max-width: 348px; }
  .e-custom-search-input { width: 234px; }
  .e-text-search-popup .e-footer-content, .e-bookmark-popup .e-footer-content { padding: 0; height: 0; }
  .search-button, .search-button:disabled, .search-button:focus, .search-button:hover { background: transparent; box-shadow: none; }
  #popup .e-dlg-content { padding-left: 0; padding-bottom: 0; }
  .e-pv-bookmarks { min-width: 234px; }
  .e-pv-current-page-number { width: 46px; height: 28px; text-align: center; }
  .material .e-pv-current-page-number { border-width: 1px; }
  .e-icons { font-family: "e-icons"; font-style: normal; font-variant: normal; font-weight: normal; line-height: 1; text-transform: none; }
  .e-pv-icon::before { font-family: 'e-icons'; }
  .e-pv-open-document-icon::before  { content: '\e91c'; }
  .e-pv-download-document-icon::before { content: '\e914'; }
  .e-pv-print-document-icon::before { content: '\e917'; }
  .e-pv-previous-page-navigation-icon::before { content: '\e910'; }
  .e-pv-next-page-navigation-icon::before { content: '\e911'; }
  .e-pv-zoom-out-icon::before { content: '\e912'; }
  .e-pv-zoom-in-icon::before { content: '\e91d'; }
  .e-pv-fit-page-icon::before { content: '\e91b'; }
  .e-btn-icon.e-pv-zoom-out-icon.e-icons { transform: rotate(90deg); }

  @font-face {
    font-family: "e-icons";
    font-style: normal;
    font-weight: normal;
    src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAAKAIAAAwAgT1MvMj8wS0QAAAEoAAAAVmNtYXDSeNLMAAABuAAAAFZnbHlmok0NtwAAAjAAAAPkaGVhZBN3pEcAAADQAAAANmhoZWEHrwNhAAAArAAAACRobXR4NsgAAAAAAYAAAAA4bG9jYQdkBmQAAAIQAAAAHm1heHABHAAwAAABCAAAACBuYW1lD0oZXgAABhQAAALBcG9zdFG4mE4AAAjYAAAAyAABAAADUv9qAFoEAAAA/+gEAAABAAAAAAAAAAAAAAAAAAAADgABAAAAAQAAxsly1F8PPPUACwPoAAAAANgsr7EAAAAA2CyvsQAAAAAEAAQAAAAACAACAAAAAAAAAAEAAAAOACQABAAAAAAAAgAAAAoACgAAAP8AAAAAAAAAAQPqAZAABQAAAnoCvAAAAIwCegK8AAAB4AAxAQIAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA6RDpHQNS/2oAWgQAAJYAAAABAAAAAAAABAAAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAAAAACAAAAAwAAABQAAwABAAAAFAAEAEIAAAAGAAQAAQAC6RLpHf//AADpEOkU//8AAAAAAAEABgAKAAAAAQACAAMABQAGAAcACAAJAAoACwAMAA0ABAAAAAAAAAAUACoAZACkAL4A7gEuAVwBcAGEAZ4ByAHyAAAAAQAAAAAD6gMuAAUAAAkBBwkBJwIAAet0/on+iXQDL/4VcwF3/olzAAEAAAAAA+oDLgAFAAATCQEXCQGJAXcBd3T+Ff4VAy/+iQF3c/4VAesAAAAAAwAAAAAEAAQAAAMADwAbAAABITUhBQ4BBy4BJz4BNx4BBRYAFzYANyYAJwYAAQACAP4AAoAE2aOj2QQE2aOj2fyEBgEh2dkBIQYG/t/Z2f7fAcCAQKPZBATZo6PZBATZo9n+3wYGASHZ2QEhBgb+3wAAAAADAAAAAAQABAAACwAXACMAAAEjFTMVMzUzNSM1IwEOAQcuASc+ATceAQUWABc2ADcmACcGAAHAwMCAwMCAAcAE2aOj2QQE2aOj2fyEBgEh2dkBIQYG/t/Z2f7fAkCAwMCAwP8Ao9kEBNmjo9kEBNmj2f7fBgYBIdnZASEGBv7fAAIAAAAAAwAEAAADAAoAADEhNSEBIQkBIREhAwD9AAEA/wABgAGA/wD/AIACAP6AAYABgAACAAAAAANABAAADgAaAAABMh4CFRElBRE0Nz4BMycGFRElBRE0JiMhIgKdCwwHBf7g/uAJBAwKdC8BoAGgX0T+BkQDgAYGCwr9YHZ2AqAOCQQGUS9D/KGrqwNfRlsAAAACAAAAAAP/BAAACwAjAAABDgEHLgEnPgE3HgEFHgEXMjY/ARcVATcBIyc3PgE1LgEnDgECgAOQbW2QAwOQbW2Q/YME2aNGfDIDJAEEYf78MyMCKi4E2aOj2QKAbZADA5BtbZADA5Bto9kELioDJDP+/GEBBCQDMnxGo9kEBNkAAA==) format('truetype');
  }
</style>

The icons are embedded in the font file used in the above code snippet.

Step 6: Add the script logic for user interactions.

Hook up toolbar button clicks and wire up PDF Viewer APIs.

import Vue from "vue";
import { PdfViewerPlugin, Toolbar, Magnification, Navigation,
  LinkAnnotation, BookmarkView, Annotation, ThumbnailView,
  Print, TextSelection, TextSearch, FormFields, FormDesigner } from "@syncfusion/ej2-vue-pdfviewer";
import { ToolbarPlugin } from "@syncfusion/ej2-vue-navigations";
Vue.use(PdfViewerPlugin);
Vue.use(ToolbarPlugin);
var toolbar;
var viewer;
var currentPageBox;

function updatePageNavigation() {
  if (viewer.currentPageNumber === 1) {
    toolbar.enableItems(document.getElementById('previousPage'), false);
    toolbar.enableItems(document.getElementById('nextPage'), true);
  } else if (viewer.currentPageNumber === viewer.pageCount) {
    toolbar.enableItems(document.getElementById('previousPage'), true);
    toolbar.enableItems(document.getElementById('nextPage'), false);
  } else {
    toolbar.enableItems(document.getElementById('previousPage'), true);
    toolbar.enableItems(document.getElementById('nextPage'), true);
  }
}

function onCurrentPageBoxKeypress(event) {
  if ((event.which < 48 || event.which > 57) && event.which !== 8 && event.which !== 13) {
    event.preventDefault();
    return false;
  } else {
    var currentPageNumber = parseInt(currentPageBox.value);
    if (event.which === 13) {
      if (currentPageNumber > 0 && currentPageNumber <= viewer.pageCount) {
        viewer.navigation.goToPage(currentPageNumber);
      } else {
        currentPageBox.value = viewer.currentPageNumber.toString();
      }
    }
    return true;
  }
}

function onCurrentPageBoxClicked() { currentPageBox.select(); }

function readFile(args) {
  let upoadedFiles = args.target.files;
  if (args.target.files[0] !== null) {
    let uploadedFile = upoadedFiles[0];
    if (uploadedFile) {
      let reader = new FileReader();
      let fileName = upoadedFiles[0].name;
      reader.readAsDataURL(uploadedFile);
      reader.onload = function (e) {
        let uploadedFileUrl = e.currentTarget.result;
        viewer.load(uploadedFileUrl, null);
        viewer.fileName = fileName;
      };
    }
  }
};

export default Vue.extend({
  data: function () {
    return {
      documentPath: "https://cdn.syncfusion.com/content/pdf/hive-succinctly.pdf",
      enableToolbar: false,
      pageNoTemplate: '<div class=""><input type="text" class="e-input-group e-pv-current-page-number" id="currentPage" /></div>',
      pageTextTemplate: '<div class=""><span class="e-pv-total-page-number" id="totalPage">of 0</span></div>'
    }
  },
  PdfViewer: [ Toolbar, Magnification, Navigation, LinkAnnotation, BookmarkView, Annotation,
               ThumbnailView, Print, TextSelection, TextSearch, FormFields, FormDesigner ],
  methods: {
    openClicked: function () { document.getElementById('fileUpload').click(); },
    previousClicked: function () { viewer.navigation.goToPreviousPage(); },
    nextClicked: function () { viewer.navigation.goToNextPage(); },
    printClicked: function () { viewer.print.print(); },
    downloadClicked: function () { viewer.download(); },
    pageFitClicked: function () { viewer.magnification.fitToPage(); },
    zoomInClicked: function () { viewer.magnification.zoomIn(); },
    zoomOutClicked: function () { viewer.magnification.zoomOut(); },
    pageChange: function () {
      currentPageBox = document.getElementById('currentPage');
      currentPageBox.value = viewer.currentPageNumber.toString();
      updatePageNavigation();
    },
    documentLoad: function () {
      viewer = this.$refs.pdfviewer.ej2Instances;
      toolbar = this.$refs.toolbar.ej2Instances;
      currentPageBox = document.getElementById('currentPage');
      currentPageBox.value = '1';
      document.getElementById('totalPage').textContent = 'of ' + viewer.pageCount;
      currentPageBox.addEventListener('keypress', onCurrentPageBoxKeypress);
      currentPageBox.addEventListener('click', onCurrentPageBoxClicked);
      document.getElementById('fileUpload').addEventListener('change', readFile, false);
      updatePageNavigation();
    }
  }
});
import Vue from "vue";
import { PdfViewerPlugin, Toolbar, Magnification, Navigation,
  LinkAnnotation, BookmarkView, Annotation, ThumbnailView,
  Print, TextSelection, TextSearch, FormFields, FormDesigner } from "@syncfusion/ej2-vue-pdfviewer";
import { ToolbarPlugin } from "@syncfusion/ej2-vue-navigations";
Vue.use(PdfViewerPlugin);
Vue.use(ToolbarPlugin);
var toolbar;
var viewer;
var currentPageBox;

function updatePageNavigation() {
  if (viewer.currentPageNumber === 1) {
    toolbar.enableItems(document.getElementById('previousPage'), false);
    toolbar.enableItems(document.getElementById('nextPage'), true);
  } else if (viewer.currentPageNumber === viewer.pageCount) {
    toolbar.enableItems(document.getElementById('previousPage'), true);
    toolbar.enableItems(document.getElementById('nextPage'), false);
  } else {
    toolbar.enableItems(document.getElementById('previousPage'), true);
    toolbar.enableItems(document.getElementById('nextPage'), true);
  }
}

function onCurrentPageBoxKeypress(event) {
  if ((event.which < 48 || event.which > 57) && event.which !== 8 && event.which !== 13) {
    event.preventDefault();
    return false;
  } else {
    var currentPageNumber = parseInt(currentPageBox.value);
    if (event.which === 13) {
      if (currentPageNumber > 0 && currentPageNumber <= viewer.pageCount) {
        viewer.navigation.goToPage(currentPageNumber);
      } else {
        currentPageBox.value = viewer.currentPageNumber.toString();
      }
    }
    return true;
  }
}

function onCurrentPageBoxClicked() { currentPageBox.select(); }

function readFile(args) {
  let upoadedFiles = args.target.files;
  if (args.target.files[0] !== null) {
    let uploadedFile = upoadedFiles[0];
    if (uploadedFile) {
      let reader = new FileReader();
      let fileName = upoadedFiles[0].name;
      reader.readAsDataURL(uploadedFile);
      reader.onload = function (e) {
        let uploadedFileUrl = e.currentTarget.result;
        viewer.load(uploadedFileUrl, null);
        viewer.fileName = fileName;
      };
    }
  }
};

export default Vue.extend({
  data: function () {
    return {
      serviceUrl: "https://document.syncfusion.com/web-services/pdf-viewer/api/pdfviewer",
      documentPath: "https://cdn.syncfusion.com/content/pdf/hive-succinctly.pdf",
      enableToolbar: false,
      pageNoTemplate: '<div class=""><input type="text" class="e-input-group e-pv-current-page-number" id="currentPage" /></div>',
      pageTextTemplate: '<div class=""><span class="e-pv-total-page-number" id="totalPage">of 0</span></div>'
    }
  },
  PdfViewer: [ Toolbar, Magnification, Navigation, LinkAnnotation, BookmarkView, Annotation,
               ThumbnailView, Print, TextSelection, TextSearch, FormFields, FormDesigner ],
  methods: {
    openClicked: function () { document.getElementById('fileUpload').click(); },
    previousClicked: function () { viewer.navigation.goToPreviousPage(); },
    nextClicked: function () { viewer.navigation.goToNextPage(); },
    printClicked: function () { viewer.print.print(); },
    downloadClicked: function () { viewer.download(); },
    pageFitClicked: function () { viewer.magnification.fitToPage(); },
    zoomInClicked: function () { viewer.magnification.zoomIn(); },
    zoomOutClicked: function () { viewer.magnification.zoomOut(); },
    pageChange: function () {
      currentPageBox = document.getElementById('currentPage');
      currentPageBox.value = viewer.currentPageNumber.toString();
      updatePageNavigation();
    },
    documentLoad: function () {
      viewer = this.$refs.pdfviewer.ej2Instances;
      toolbar = this.$refs.toolbar.ej2Instances;
      currentPageBox = document.getElementById('currentPage');
      currentPageBox.value = '1';
      document.getElementById('totalPage').textContent = 'of ' + viewer.pageCount;
      currentPageBox.addEventListener('keypress', onCurrentPageBoxKeypress);
      currentPageBox.addEventListener('click', onCurrentPageBoxClicked);
      document.getElementById('fileUpload').addEventListener('change', readFile, false);
      updatePageNavigation();
    }
  }
});

Sample:
https://document.syncfusion.com/demos/pdf-viewer/vue/#/tailwind3/pdfviewer/custom-toolbar.html

See also