Skip to content

Set form data - Vuejs

In this example we set form data in a PDF form. You can setup one-way or two-way data bindings between the form fields in the PDF document and input elements in html.

Prerequisites

This example assumes that there exists a document form.pdf in your storage. If you do not have such a document you can run the following command to add a document with fields under that name:

shell
node src/post-document.js

Reading the form fields and form field definitions

In the handler for the loaded event you can retrieve the field definitions and form data.

ts
<script setup lang="ts">
import { ref, onMounted, nextTick } from "vue";
import {
  PdfApplication,
  PdfDocument,
  PdfPages,
  type IPdfDocument,
  type PdfDocumentCustomEvent,
  type IPdfField,
} from "@tallcomponents/unopdf-vue";
import PdfInput from "./PdfInput.vue";

// Note: this example uses Vite and retrieves the keys from the environment.
// This should not be used in production.
// See the authentication section in the usage guide for more information.

const publickey = import.meta.env.VITE_PUBLIC_KEY;
const privatekey = import.meta.env.VITE_PRIVATE_KEY;

const app = ref<{ $el: HTMLPdfApplicationElement }>();
const document = ref<{ $el: HTMLPdfDocumentElement }>();
const pages = ref<{ $el: HTMLPdfPagesElement }>();
const formDocument = ref<IPdfDocument>();
const fieldDefinitions = ref<IPdfField[] | null | undefined>([]);

const formData = ref<Record<string, string>>({});

onMounted(async () => {
  await nextTick();
  const _app = app.value?.$el;
  if (_app) {
    formDocument.value = (await _app.getDocuments()).find((doc) => doc.originalFileName == "form.pdf");
    const _doc = document.value?.$el;

    if (_doc && formDocument.value && formDocument.value.id) {
      await _doc.open(formDocument.value.id);
    }
  }
});

// #region on-loaded
const onLoaded = async ({ target }: PdfDocumentCustomEvent<string | null>) => {
  fieldDefinitions.value = await target.getFields();
  formData.value = await target.getFormData();
};
// #endregion on-loaded

const onChanged = (value: Record<string,string>) => {
  // store form data
  const _pdf = document.value?.$el as HTMLPdfDocumentElement;
  _pdf.setFormData(value);
  formData.value = value;
}


// #region set-form-data-on-change
const onFormDataChanged = async ({ target }: PdfDocumentCustomEvent<unknown>) => {
  formData.value = await target.getFormData();
};
// #endregion set-form-data-on-change

// #region save-document-handler
const saveDocument = async () => {
  const _doc = document.value?.$el;
  if (_doc) {
    await _doc.save();
    const responseDocument = await _doc.save();
    if (responseDocument) {
      console.log(`document saved with id ${responseDocument.id}`);
    } else {
      console.log(`document not saved`);
    }
    console.log('form data', (await _doc.getFormData()));
  }
}
// #endregion save-document-handler

const downloadDocument = () => {
  document.value?.$el.download()
}

</script>

<template>
  <h1>UnoPdf Example: Fill Form Fields in Document from HTML input fields in Vue</h1>

  <PdfApplication
    ref="app"
    :publickey="publickey"
    :privatekey="privatekey"
  />

  <!-- #region pdf-document -->
  <PdfDocument
    ref="document"
    @loaded="onLoaded"
    @formdatachanged="onFormDataChanged"
  />
  <!-- #endregion pdf-document -->

  <div style="display: flex">
    <div>
      <PdfPages ref="pages" />
    </div>
    <div>
      <div style="padding: 10px">
        You can fill out the fields in the document and see your changes reflected below
      </div>
      <div style="padding: 10px">
        <h2>
          Fields
        </h2>
        <div
          v-for="(value,name) in formData"
          :key="name"
          style="padding-top:10px"
        >
          <div>{{ name }}: {{ value }}</div>
          <PdfInput
            :form-data="formData"
            :field-definitions="fieldDefinitions!"
            :name="name"
            @change="onChanged"
          />
        </div>
      </div>
      <div style="display: flex">
        <div style="padding: 10px">
          <!-- #region save-document-button -->
          <button
            type="button"
            style="font-size: 20px"
            @click="saveDocument"
          >
            save
          </button>
        <!-- #endregion save-document-button -->
        </div>
        <div style="padding: 10px">
          <button
            type="button"
            style="font-size: 20px"
            @click="downloadDocument"
          >
            download
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

When you want to show an input element in your html for a PDF field you can retrieve the field type from the field definition:

ts
const fieldType = computed(() => fieldDefinition.value?.fieldType)

Depending on the field type there are other fields available in the field definition such as checkBoxOnValue, checkBoxOffValue, listOptions and radioButtonOptions

ts
const radioButtonOptions = computed(() => fieldDefinition.value?.radioButtonOptions)
const listOptions = computed(() => fieldDefinition.value?.listOptions)

const checkboxOnValue = computed(() => fieldDefinition.value?.checkBoxOnValue || 'On');
const checkboxOffValue = computed(() => fieldDefinition.value?.checkBoxOffValue || 'Off');

By combining this information you can dynamically determine which type of HTML input to use.

html
<div v-if="fieldType == 'RadioButtonField'">
  <template
    v-for="option in radioButtonOptions"
    :key="option"
  >
    <input
      v-model="value"
      type="radio"
      :value="option"
      @input="event => setValue((event.target as HTMLInputElement).value)"
    > {{ option }}
  </template>
</div>
<div v-else-if="fieldType == 'TextField'">
  <input
    type="text"
    :value="value"
    @input="event => setValue((event.target as HTMLInputElement).value)"
  >
</div>
<div v-else-if="fieldType == 'ListBoxField' || fieldType == 'DropDownListField'">
  <select
    v-model="value"
    :name="name"
    :size="fieldType == 'ListBoxField' ? listOptions?.length || 0 : undefined"
    @change="event => setValue((event.target as HTMLSelectElement).value)"
  >
    <template
      v-for="option in listOptions"
      :key="option.export"
    >
      <option
        :value="option.export"
      >
        {{ option.display }}
      </option>
    </template>
  </select>
</div>
<div v-else-if="fieldType == 'CheckBoxField'">
  <input
    type="checkbox"
    :value="value"
    :checked="checked"
    @input="onChange"
  >
</div>

Screenshot

Screenshot op set-form-data

Code

vue
<script setup lang="ts">
import { ref, onMounted, nextTick } from "vue";
import {
  PdfApplication,
  PdfDocument,
  PdfPages,
  type IPdfDocument,
  type PdfDocumentCustomEvent,
  type IPdfField,
} from "@tallcomponents/unopdf-vue";
import PdfInput from "./PdfInput.vue";

// Note: this example uses Vite and retrieves the keys from the environment.
// This should not be used in production.
// See the authentication section in the usage guide for more information.

const publickey = import.meta.env.VITE_PUBLIC_KEY;
const privatekey = import.meta.env.VITE_PRIVATE_KEY;

const app = ref<{ $el: HTMLPdfApplicationElement }>();
const document = ref<{ $el: HTMLPdfDocumentElement }>();
const pages = ref<{ $el: HTMLPdfPagesElement }>();
const formDocument = ref<IPdfDocument>();
const fieldDefinitions = ref<IPdfField[] | null | undefined>([]);

const formData = ref<Record<string, string>>({});

onMounted(async () => {
  await nextTick();
  const _app = app.value?.$el;
  if (_app) {
    formDocument.value = (await _app.getDocuments()).find((doc) => doc.originalFileName == "form.pdf");
    const _doc = document.value?.$el;

    if (_doc && formDocument.value && formDocument.value.id) {
      await _doc.open(formDocument.value.id);
    }
  }
});

// #region on-loaded
const onLoaded = async ({ target }: PdfDocumentCustomEvent<string | null>) => {
  fieldDefinitions.value = await target.getFields();
  formData.value = await target.getFormData();
};
// #endregion on-loaded

const onChanged = (value: Record<string,string>) => {
  // store form data
  const _pdf = document.value?.$el as HTMLPdfDocumentElement;
  _pdf.setFormData(value);
  formData.value = value;
}


// #region set-form-data-on-change
const onFormDataChanged = async ({ target }: PdfDocumentCustomEvent<unknown>) => {
  formData.value = await target.getFormData();
};
// #endregion set-form-data-on-change

// #region save-document-handler
const saveDocument = async () => {
  const _doc = document.value?.$el;
  if (_doc) {
    await _doc.save();
    const responseDocument = await _doc.save();
    if (responseDocument) {
      console.log(`document saved with id ${responseDocument.id}`);
    } else {
      console.log(`document not saved`);
    }
    console.log('form data', (await _doc.getFormData()));
  }
}
// #endregion save-document-handler

const downloadDocument = () => {
  document.value?.$el.download()
}

</script>

<template>
  <h1>UnoPdf Example: Fill Form Fields in Document from HTML input fields in Vue</h1>

  <PdfApplication
    ref="app"
    :publickey="publickey"
    :privatekey="privatekey"
  />

  <!-- #region pdf-document -->
  <PdfDocument
    ref="document"
    @loaded="onLoaded"
    @formdatachanged="onFormDataChanged"
  />
  <!-- #endregion pdf-document -->

  <div style="display: flex">
    <div>
      <PdfPages ref="pages" />
    </div>
    <div>
      <div style="padding: 10px">
        You can fill out the fields in the document and see your changes reflected below
      </div>
      <div style="padding: 10px">
        <h2>
          Fields
        </h2>
        <div
          v-for="(value,name) in formData"
          :key="name"
          style="padding-top:10px"
        >
          <div>{{ name }}: {{ value }}</div>
          <PdfInput
            :form-data="formData"
            :field-definitions="fieldDefinitions!"
            :name="name"
            @change="onChanged"
          />
        </div>
      </div>
      <div style="display: flex">
        <div style="padding: 10px">
          <!-- #region save-document-button -->
          <button
            type="button"
            style="font-size: 20px"
            @click="saveDocument"
          >
            save
          </button>
        <!-- #endregion save-document-button -->
        </div>
        <div style="padding: 10px">
          <button
            type="button"
            style="font-size: 20px"
            @click="downloadDocument"
          >
            download
          </button>
        </div>
      </div>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
import type { IPdfField } from '@tallcomponents/unopdf-core';
import { computed } from 'vue';

interface Props {
  name: string;
  fieldDefinitions: IPdfField[];
  formData: Record<string, string>
}

const props = defineProps<Props>();
const emit = defineEmits<{ change: [Record<string, string>] }>()

if (!Object.keys(props.formData).includes(props.name)) {
  throw new Error(`unknown form field ${props.name}`)
}

const fieldDefinition = computed(() => props.fieldDefinitions.find((f) => f.fullName === props.name));
if (!fieldDefinition.value) {
  throw new Error(`no field definition for ${props.name}`);
}

// #region field-type
const fieldType = computed(() => fieldDefinition.value?.fieldType)
// #endregion field-type

// #region read-field-options
const radioButtonOptions = computed(() => fieldDefinition.value?.radioButtonOptions)
const listOptions = computed(() => fieldDefinition.value?.listOptions)

const checkboxOnValue = computed(() => fieldDefinition.value?.checkBoxOnValue || 'On');
const checkboxOffValue = computed(() => fieldDefinition.value?.checkBoxOffValue || 'Off');
// #endregion read-field-options

const setValue =
  (value: string) => {
    const f = { ...props.formData, [props.name]: value };
    emit('change', f);
  }

const value = computed(() => props.formData[props.name]);
const checked = computed(() => value.value === checkboxOnValue.value)

const onChange = () => {
  if (props.formData[props.name] === checkboxOnValue.value) {
    setValue(checkboxOffValue.value)
  } else {
    setValue(checkboxOnValue.value)
  }
}
</script>

<template>
  <!-- #region input-field -->
  <div v-if="fieldType == 'RadioButtonField'">
    <template
      v-for="option in radioButtonOptions"
      :key="option"
    >
      <input
        v-model="value"
        type="radio"
        :value="option"
        @input="event => setValue((event.target as HTMLInputElement).value)"
      > {{ option }}
    </template>
  </div>
  <div v-else-if="fieldType == 'TextField'">
    <input
      type="text"
      :value="value"
      @input="event => setValue((event.target as HTMLInputElement).value)"
    >
  </div>
  <div v-else-if="fieldType == 'ListBoxField' || fieldType == 'DropDownListField'">
    <select
      v-model="value"
      :name="name"
      :size="fieldType == 'ListBoxField' ? listOptions?.length || 0 : undefined"
      @change="event => setValue((event.target as HTMLSelectElement).value)"
    >
      <template
        v-for="option in listOptions"
        :key="option.export"
      >
        <option
          :value="option.export"
        >
          {{ option.display }}
        </option>
      </template>
    </select>
  </div>
  <div v-else-if="fieldType == 'CheckBoxField'">
    <input
      type="checkbox"
      :value="value"
      :checked="checked"
      @input="onChange"
    >
  </div>
  <!-- #endregion input-field -->
  <div v-else>
    Unsupported Field Type: {{ fieldType }}
  </div>
</template>

Running the example

  • Download the vue - Set form data project
  • Unzip the file to a directory vue-set-form-data.
    shell
    unzip vue-set-form-data.zip -d vue-set-form-data
  • Open a terminal and go to that directory
    shell
    cd vue-set-form-data
  • Install dependencies
    shell
    npm install
    shell
    yarn
  • Start the project
    shell
    npm run start
    shell
    yarn start