One request that shows up again and again on the Adobe Forums (or in the past on AcrobatUsers.com) is about how to generate a unique serial number or form number in a PDF file. The scenario is usually something like this: A user creates documents (e.g. quotes or orders) that need to be numbered in a sequential manner so that no forms will use the same number. There are a number of solution available that work well as long as all these forms are always processed by the same user on the same computer. This is accomplished by storing the number in JavaScript in e.g. another form, or in global JavaScript data.
How can the same functionality be implemented if the requirement is to use this on multiple computers, or by multiple users? Allowing access from multiple environments complicates things quite a bit. To allow concurrent access to the “serial number generator”, we need to move the solution to a server. There are different mechanisms how Acrobat can access a server, for the following discussion, I want to use a web service via the SOAP interface, which is documented in the Acrobat JavaScript API: SOAP and Web Services
Explaining what SOAP is and what it’s used for is outside of the scope of this post. If you want to learn more about SOAP, take a look here: SOAP on Wikipedia
Implementing such a solution requires two parts:
- A JavaScript program running in the form
- The actual web service running on a server.
When I say “server’, I use that term in a very broad sense, and that could mean that you run the software on an actual server (running e.g. Windows Server, Mac OS Server, Linux, ..) or on a normal workstation that is running server software. You can install for example the free XAMPP software on a Windows 10 computer and still get the same behavior as you would with a real server system.
Before we get too far into this, when you review the requirements for the SOAP calls in Acrobat’s JavaScript implementation, you will notice the “F” in the quick bar:
This means that this feature is only available in the free Adobe Reader if “Forms” rights are applied to the document. Even though Adobe Acrobat can apply some extended Reader rights to a document, for the “Forms” rights, the LiveCycle Reader Extensions software is necessary. For the purpose of this tutorial, the presented solution will only work if you are using Adobe Acrobat (version 6 and later) and not the free Adobe Reader.
The solution we want to create is a form that has at least two fields: One that will eventually contain the form number – or serial number – and one that contains the name of the person filling out the form, or the name of the person the form was created for. In addition to these two, the form can contain any number of fields. We also need a mechanism to request a new serial number – in the sample below, that will be done using a button. When a new serial number is generated, I also want to store the user name and the current time and date so that I can go back later and find out what documents were processed and either by what user, or for which customer (depending on how I use that user name field).
To make things easier on the implementation in the actual PDF form, let’s start with creating the web service that provides these unique numbers and stores information about the request to generate such a number.
This web service should provide a function named “getSerialNumber()”, which takes one argument (the user name), and returns a string containing a new number. Web services can be written in any number of languages. Oftentimes web services are written in Java and are then executed in an application server like Apache Tomcat. For this sample implementation, I selected a web service written in PHP. This has the advantage that it will run on Windows, Linux, Mac OS, and any other system that provides support for PHP. The XAMPP system mentioned above does come with a PHP interpreter and Apache’s web server configured to use PHP. Setting up a web service in PHP based on just the documentation is not straight forward, and I found a lot of help here: A Complete PHP SOAP Server Example If you want to understand more about how all these pieces are working together, you may want to spend some time on that site.
The big question now is how can one create a unique and sequential number? I am going to use a database for that: When you define a field as the primary key in a MySQL database, you end up with such a unique and sequential number. For every new record, that index gets automatically incremented, starting with the value 1 for the first record.
The following PHP script implements such a system that writes the user name and the current date to a database, and then returns the index for that new record:
<?php function getNextSerial($userName) { // open a database connection and insert a new record, get the last used index and return that $mysqli = new mysqli("localhost", "theUser", "thePassword", "serialnumbers"); // check connection if (mysqli_connect_errno()) { printf("Connect failed: %s\n", mysqli_connect_error()); exit(); } $query = "INSERT INTO serialnumbers (username, date) VALUES (\"" . $mysqli->real_escape_string($userName) . "\", NOW())"; $mysqli->query($query); $idx = $mysqli->insert_id; // get the unique index of the just inserted record // close connection $mysqli->close(); return $idx; } // Disable the wsdl cache ini_set("soap.wsdl_cache_enabled", "0"); // Define the response class for getSerial() class getSerialResponse{ public $return; } // Define the class and methods for the object that gets passed to setClass class getSerialClass{ public function getSerialNumber($parameters){ // Instantiate the response class $response = new getSerialResponse(); // Return the next available serial number $response->return = getNextSerial($parameters->userName); return $response; } } // Create a new server $server = new SoapServer("GetSerialNumber.wsdl"); // Set the class for the server $server->setClass("getSerialClass"); // Handle the soap operations $server->handle(); ?>
This assumes that a WSDL file named “GetSerialNumber.wsdl” is in the same directory as this PHP file, and this WSDL file in turn is referencing a schema file that also needs to be in the same directory.
You can learn about how to manually create a WSDL file here: How to Generate WSDL for PHP SOAP Server – With a recent version of NetBeans, there is one more step involved that is not documented in the tutorial: You need to open the generated WSDL and replace “REPLACE_WITH_ACTUAL_URL” with the actual URL of your web service PHP file.
You can download all files referenced in this post here: GetSerial.zip
So, what does this do? It defines one web service routine called “getSerialNumber()” which takes one string parameter (the user name) and returns a string
containing the new serial number. It does that by calling the “getNextSerial()” function, which opens a connection to a MySQL database and inserts a new record using that user name and the current time and date. It then returns the index of the just inserted record.
Here are the MySQL commands to create the database, the table and a user that can modify the table:
CREATE DATABASE serialnumbers; CREATE TABLE serialnumbers (idx INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, username VARCHAR(30), date TIMESTAMP); CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password'; GRANT INSERT,SELECT ON serialnumbers to 'theUser'@'localhost';
Now to the JavaScript code that runs in the PDF form: In my sample document, I have two fields, one name field, and one serial number field plus a button that when pressed takes the information from the user name field, generates a new serial number and populates the respective field and then hides the button so that you cannot keep on clicking on it and therefore “waste” serial numbers.
// Obtain the WSDL proxy object: var myProxy = Net.SOAP.connect("http://localhost/GetSerial/GetSerialNumber.wsdl?wsdl"); // get the user name from a field var userName = this.getField("UserName").value; if (userName != "") { var result = myProxy.getSerialNumber(userName); this.getField("Result").value = util.printf("%04d", Number(result)); // hide this button event.target.display = display.hidden; } else { app.alert("Please fill in a user name"); }
In the first step, we load the WSDL file that was installed with our PHP web service, then we get the contents of the “UserName” field and if that is a non empty string, we request a new serial number via the SOAP proxy object. We then take that result, interpret it as a number and format it so that it has leading zeros. The last thing we do is to hide the button that was used to generate the serial number. There could be more error checking and exception handling in this code, but I did not want to clutter up the actual functionality with that. For example, you need to check that the user name provided is not longer than 30 characters (that’s what we used to define the length of the VARCHAR field in the database table).
When you try to use these files, you have to make sure that you adjust all URLs that are used in both JavaScript and PHP code so that they match your installation. I had things installed in http://localhost/GetSerial – you would have to search for that string and replace it with the correct path to the directory in your installation.
My tutorials are usually pretty simple to follow. This is a bit more complex and requires you to setup a web server, the PHP system running in that server, a database server with a database and then at the very end a few lines of JavaScript code in a PDF form. I have to assume that whoever wants to tackle this does know how to work with all these different technologies. If this is a bit too much for you, please ask me for a quote to implement such a solution for you.
Good Evening! I saw your example and tried to put in my localhost. Upon checking, the username value is not passed to the parameter of getSerialNumber and not saving to the database. If you already find the solution to this please reply. Thanks
RDB, all I can say is that it worked for me on my system. I am not a SOAP expert, so that means that you are on your own to find a solution. However, if you don’t need SOAP, but just a way to get data in and out of a database, you may want to consider a FDF based solution: http://khkonsulting.com/2017/08/connect-database-pdf-form-time-without-soap/