0

I am attempting to do a small PoC with PDFs and have run into an issue. I am looking to post a message to a PDF and have the PDF post a message to the browser.

The deets:

I am viewing the PDF in an "object" element in IE9. I am using itextsharp to prefill a pdf template on the server, inject some app level javascript (post message and on message stuff) and then serve that up to the browser via a filestreamresult. I am using Reader 10 to view the PDF in IE9.

What works:

So far, everything works except for the PDF posting a message to the browser. I can post a message to the PDF, from the browser, no problem and all of the fields are prefilled as desired.

What doesn't work:

When I try using this.hostContainer.postMessage(["something","somethingmore"]) I get an Acrobat Escript window that says "hostContainer is not defined". I have also tried using "event.target.hostContainer" but I get "event.target is not defined". I am at a loss of what to do and any insight would be super helpful.

Reference links:

  1. Acrobat Javascript API
  2. Stackoverflow How-To on this topic
  3. Original guide I used

The code:

My form view:

<object id="pdfFrame" style="width:100%;height: 100%;" data="@Url.Action("LoadForm")">No luck :(</object>

My custom javascript string method:

private static string GetCustomJavascript(string existingJavaScript)
    {
        const string newJs = 
        "this.disclosed = true; " +
        "if (this.external && this.hostContainer) { " +
            "function onMessageFunc( stringArray ) { " +
//                    "var name = this.myDoc.getField(personal.name); " +
//                    "var login = this.myDoc.getField(personal.loginname); " +
                    "try{" +
                        "app.alert(doc.xfa);" +
                        "console.println('Doc xfa value = ' + doc.xfa);" +
//                            "event.target.hostContainer.postMessage(['hello from pdf!']);" +
//                        "this.hostContainer.postMessage(['hello from pdf!']);"+
//                        "name.value = stringArray[0]; " +
//                        "login.value = stringArray[1]; " +
                    "} catch(e){ onErrorFunc(e); } " +
                "} " +
                "function onErrorFunc( e ) { " +
                    "console.show(); " +
                    "console.println(e.toString()); " +
                "} " +
                "try {" +
                    "if(!this.hostContainer.messageHandler) { " +
                        "this.hostContainer.messageHandler = new Object(); " +
                        "this.hostContainer.messageHandler.myDoc = this; " +
                        "this.hostContainer.messageHandler.onMessage = onMessageFunc; " +
                        "this.hostContainer.messageHandler.onError = onErrorFunc; " +
                        "this.hostContainer.messageHandler.onDisclose = function(){ return true; }; " +
                    "}" +
                "} catch(e){onErrorFunc(e);}" +
            "}";
            var jsToReturn = existingJavaScript + newJs;
            return jsToReturn;
        }

My method for filling and sending the form to the browser:

public MemoryStream GetFilledRequestForm(string fileDirectory, User user, FormView formView)
        {
            var pdfStream = new MemoryStream();
            var templateFilePath = GetRequestTypeTemplateFilePath(fileDirectory, _requestModule.FormTemplateFileName);
            var pdfReader = new PdfReader(templateFilePath);
//            pdfReader.RemoveUsageRights();
            var stamper = new PdfStamper(pdfReader, pdfStream);
            var formFields = GetFormFields(user, formView, pdfReader);
            foreach (var field in formFields.Where(f => f.Value != null))
            {
                stamper.AcroFields.SetField(field.Name, field.Value);
            }
            stamper.FormFlattening = false;
            var newJs =  GetCustomJavascript(stamper.Reader.JavaScript);
            stamper.AddJavaScript("newJs", newJs);
            stamper.Close();
            byte[] byteInfo = pdfStream.ToArray();
            var outputStream = new MemoryStream();
            outputStream.Write(byteInfo, 0, byteInfo.Length);
            outputStream.Position = 0;
            return outputStream;
        }
Community
  • 1
  • 1
JasonWilczak
  • 2,303
  • 2
  • 21
  • 37
  • You might have seen this already but some people are talking the document mode and using the legacy class id attribute, maybe give that a shot. https://forums.adobe.com/thread/1188852?tstart=0 – Chris Haas Feb 25 '15 at 15:10
  • I actually am just running into this. How does one determine what the classid value should be? – JasonWilczak Feb 25 '15 at 15:17
  • The class id is what is listed in that thread, `clsid:CA8A9780-280D-11CF-A24D-444553540000`. You can actually find that GUID by looking in the Windows registry under `HKCR\AcroPDF.PDF\CLSID`. That GUID is common across all computers that have Acrobat Reader installed. – Chris Haas Feb 25 '15 at 15:41
  • Ok, yes, perfect, thank you. I added that into my object element which now looks like this: No luck :( but still it doesn't not work. – JasonWilczak Feb 25 '15 at 15:52
  • I have finally gotten it to work, I'm going to clean up my code and post an answer shortly. – JasonWilczak Feb 25 '15 at 18:10

1 Answers1

0

Ok, so I have resolved it, with some help of course. I found the key at this stack overflow post. I needed to wait for the object to load before assigning the message handler. Additionally, I needed a global variable in the pdf javascript to be able to post the message.

Html/Javascript: (the key here is the loadListener() function)

    @model WebModel.FormView
<object id="pdfFrame" style="width:100%;height: 100%;" data="@Url.Action("LoadForm")">No luck :(</object>
    <input id="buttonPost" type="button" value="post to pdf"/>
    <script type="text/javascript">
        var PDFObject = document.getElementById("pdfFrame");
        function loadListener() {
            if (typeof PDFObject.readyState === 'undefined') { // ready state only works for IE, which is good because we only need to do this for IE because IE sucks in the first place
                debugger;
                PDFObject.messageHandler = { onMessage: messageFunc };
                return;
            }
            if (PDFObject.readyState == 4) {
                debugger;
                PDFObject.messageHandler = { onMessage: messageFunc };
            } else {
                setTimeout(loadListener, 500);
            }
        }
        function messageFunc(data) {
            debugger;
            var messagedata = data;
            alert('finally!!');
        }

        function sendToPdf() {
            if(PDFObject!= null){
                PDFObject.postMessage(
                ["a", "b"]);
            }
        }

        $('#pdfFrame').ready(function() {
            loadListener();
            $('#buttonPost').on('click', function() {
                sendToPdf();
            });
        });

    </script>

My new function to create the javascript: (the key here is var appHostContainer)

private static string GetCustomJavascript(string existingJavaScript)
        {
            const string newJs = 
            "this.disclosed = true; " +
            "var appHostContainer = this.hostContainer;" + 
            "if (this.external && this.hostContainer) { " +
                "function onMessageFunc( stringArray ) { " +
//                    "var name = this.myDoc.getField(personal.name); " +
//                    "var login = this.myDoc.getField(personal.loginname); " +
                    "try{" +
                        "app.alert(stringArray);" +
                        "appHostContainer.postMessage(['hello from pdf!']);" +
//                        "name.value = stringArray[0]; " +
//                        "login.value = stringArray[1]; " +
                    "} catch(e){ onErrorFunc(e); } " +
                "} " +
                "function onErrorFunc( e ) { " +
                    "console.show(); " +
                    "console.println(e.toString()); " +
                "} " +
                "try {" +
                    "if(!this.hostContainer.messageHandler) { " +
                        "this.hostContainer.messageHandler = new Object(); " +
                        "this.hostContainer.messageHandler.myDoc = this; " +
                        "this.hostContainer.messageHandler.onMessage = onMessageFunc; " +
                        "this.hostContainer.messageHandler.onError = onErrorFunc; " +
                        "this.hostContainer.messageHandler.onDisclose = function(){ return true; }; " +
                    "}" +
                "} catch(e){onErrorFunc(e);}" +
            "}";
            var jsToReturn = existingJavaScript + newJs;
            return jsToReturn;
        }
JasonWilczak
  • 2,303
  • 2
  • 21
  • 37
  • The code seems to have a problem: I have to change "PDFObject.messageHandler = { onMessage: messageFunc };" to "PDFObject.messageHandler = { onMessage: messageFunc() };" otherwise it doesn't work for me – Robert Jul 03 '15 at 10:29
  • @Robert I'm not sure why that would work. Your code would execute the messageFunc() and set the result to "onMessage" property... that is strange. Are there any hidden syntax errors? – JasonWilczak Jul 03 '15 at 14:15
  • @JasonWolczak: I agree to you. I think it "works", because it doesn't use the messageHandler mechanism. My loadListener function is called every 500 milliseconds and if I reach ready state it calls messageFunc() directly. The printer.messageHandler = {onMessage:...} can be dropped in my case. I've to check how to get it work correctly. – Robert Jul 06 '15 at 07:47