Monday, April 19, 2010

ColdFusion: iText / Add JavaScript To Form Example

I occasionally see questions about adding javascript to existing pdf's. There are (of course) some great examples on the iText site. I thought this CF translation of the AddJavaScriptToForm example might be helpful. If you review the code and comments, it actually simpler than it looks. There are some minor differences to compensate for the older iText jars in CF9 and CF8. As well as some name changes to avoid reserved word conflicts.

Source: http://1t3xt.info/examples/browse/?page=example&id=438


Javascript Code (CF8 + CF9)

<cfsavecontent variable="jsCode">
    function setReadOnly(readonly) {
        var partner = this.getField('partner');
        if(readonly) {
            partner.value = '';
        }
        partner.readonly = readonly;
    }
    function validate() {
        var married = this.getField('married');
        var partner = this.getField('partner');
        if (married.value == 'yes' && partner.value == '') {
            app.alert('please enter the name of your partner');
        }
        else {
            var prop = new Object();
            prop.cURL = "http://1t3xt.info/examples/request.php";
            prop.cSubmitAs = "HTML";
            this.submitForm( {  
                                cURL: "http://1t3xt.info/examples/request.php",
                                cSubmitAs: "HTML"
                              } 
                            );            
        }
    }
</cfsavecontent>

1) Create a Form (CF9 + CF8)
Note: CF8 does not support "finally". So for compatibility, just change the try/finally clause to a try/catch.
<cfscript>
    outputPath     = ExpandPath("form_without_js.pdf");
    document = createObject("java", "com.lowagie.text.Document").init();

    try {
        stream = createObject("java", "java.io.FileOutputStream").init( outputPath );
        writer = createObject("java", "com.lowagie.text.pdf.PdfWriter").getInstance(document, stream);
        document.open();
         
         Element = createObject("java", "com.lowagie.text.Element");
        BaseFont = createObject("java", "com.lowagie.text.pdf.BaseFont");
        //Note: CF8 + CF9 iText versions do not have a createFont() method with zero params
        bf = BaseFont.createFont( BaseFont.HELVETICA, BaseFont.WINANSI, false );
        directcontent = writer.getDirectContent();
        directcontent.beginText();
        directcontent.setFontAndSize(bf, 12);
        directcontent.showTextAligned( Element.ALIGN_LEFT, "Married?", 36, 770, 0 );
        directcontent.showTextAligned( Element.ALIGN_LEFT, "YES", 58, 750, 0);
        directcontent.showTextAligned( Element.ALIGN_LEFT, "NO", 102, 750, 0);
        directcontent.showTextAligned( Element.ALIGN_LEFT, "Name partner?", 36, 730, 0 );
        directcontent.endText();

        // initialize reusable objects
         Rectangle = createObject("java", "com.lowagie.text.Rectangle");
         PdfFormField = createObject("java", "com.lowagie.text.pdf.PdfFormField");
         RadioCheckField = createObject("java", "com.lowagie.text.pdf.RadioCheckField");
         TextField = createObject("java", "com.lowagie.text.pdf.TextField");
         Color = createObject("java", "java.awt.Color");

        married = PdfFormField.createRadioButton(writer, true);
        married.setFieldName("married");
        writer.addAnnotation( married );
        
        // Note: Field names changed to avoid CF reserved word conflicts (ie "yes", "no")
        married.setValueAsName("yes");
        rectYes = Rectangle.init( 40, 766, 56, 744 );
        yesField = RadioCheckField.init(writer, rectYes, javacast("null", ""), "yes");
        yesField.setChecked(true);
        married.addKid( yesField.getRadioField() );
        rectNo = Rectangle.init( 84, 766, 100, 744 );
        noField = RadioCheckField.init(writer, rectNo, javacast("null", ""), "no");
        noField.setChecked(false);
        married.addKid( noField.getRadioField() );
        writer.addAnnotation( married );
 
         rect = Rectangle.init( 40, 710, 200, 726 );
        partner = TextField.init( writer, rect, "partner" );
        partner.setBorderColor( Color.BLACK );
        partner.setBorderWidth( 0.5 );
        writer.addAnnotation( partner.getTextField() );
    
        document.close();
        WriteOutput("File created! "& outputPath &"<hr>");
    }
       // cleanup  
    finally {
        if (isDefined("document")) {
            document.close();
        }        
        if (isDefined("stream")) {
            stream.close();
        }        
    }
</cfscript>

2) Add the Javascript (CF9 Only)
<cfscript>
    inputPath     = ExpandPath("form_without_js.pdf");
    outputPath     = ExpandPath("form_plus_js.pdf");
    document = createObject("java", "com.lowagie.text.Document").init();
    
    try {
        // read in the pdf form
        reader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( inputPath );
        stream = createObject("java", "java.io.FileOutputStream").init( outputPath );
        stamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init( reader, stream );

        // add javascript functions to the document
        stamper.getWriter().addJavaScript( jsCode );

        // create reference objects
        PdfName = createObject("java", "com.lowagie.text.pdf.PdfName");
        PdfAction = createObject("java", "com.lowagie.text.pdf.PdfAction");
        PdfDictionary = createObject("java", "com.lowagie.text.pdf.PdfDictionary");
        PushbuttonField = createObject("java", "com.lowagie.text.pdf.PushbuttonField");
        
        // extract the parent option from the form 
        formObj = stamper.getAcroFields();
        fd = formObj.getFieldItem("married");

        // retrieve the dictionaries for "yes" radio button
        // note: CF9 iText version does not have the getWidgetRef(index) method
         dictYes = reader.getPdfObject( fd.widget_refs.get(0) );
        yesAction = dictYes.getAsDict( PdfName.AA );
        if (not IsDefined("yesAction")) {
            yesAction = PdfDictionary.init();
        }
        // add an onFocus event to this field
        yesAction.put( PdfName.init("Fo"), PdfAction.javaScript("setReadOnly(false);", stamper.getWriter()));
        dictYes.put( PdfName.AA, yesAction );

        // retrieve the dictionaries for "no" radio button
        dictNo = reader.getPdfObject( fd.widget_refs.get(1));
        noAction = dictNo.getAsDict( PdfName.AA );
        if (not IsDefined("noAction")) {
            noAction = PdfDictionary.init();
        }    
        // add an onFocus event to this field
        noAction.put( PdfName.init("Fo"), PdfAction.javaScript("setReadOnly(true);", stamper.getWriter()));
        dictNo.put(PdfName.AA, noAction);
 
         // create a submit button
        writer = stamper.getWriter();
        button = PushbuttonField.init(    writer, Rectangle.init(40, 690, 200, 710), "submit" );
        button.setText( "validate and submit" );
        button.setOptions( PushbuttonField.VISIBLE_BUT_DOES_NOT_PRINT );
        validateAndSubmit = button.getField();
        // this will call the validate function when the button is clicked
        validateAndSubmit.setAction( PdfAction.javaScript("validate();", stamper.getWriter()) );
        // add the button to page 1
        stamper.addAnnotation(validateAndSubmit, 1);

        WriteOutput("File created! "& outputPath &"<hr>");
    }
    finally {
        // cleanup
        if (isDefined("stamper")) {
            stamper.close();
        }        
        if (isDefined("stream")) {
            stream.close();
        }        
    }
</cfscript>

2) Add the Javascript (CF8 Only)
<cfscript>
    inputPath     = ExpandPath("form_without_js.pdf");
    outputPath     = ExpandPath("form_plus_js.pdf");
    document = createObject("java", "com.lowagie.text.Document").init();
    
    try {
        // read in the pdf form
        reader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( inputPath );
        stream = createObject("java", "java.io.FileOutputStream").init( outputPath );
        stamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init( reader, stream );

        // add javascript functions to the document
        stamper.getWriter().addJavaScript( jsCode );

        // create reference objects
        PdfName = createObject("java", "com.lowagie.text.pdf.PdfName");
        PdfAction = createObject("java", "com.lowagie.text.pdf.PdfAction");
        PdfDictionary = createObject("java", "com.lowagie.text.pdf.PdfDictionary");
        PushbuttonField = createObject("java", "com.lowagie.text.pdf.PushbuttonField");
        
        // prepare to extract form field objects
        formObj = stamper.getAcroFields();
        
        // get parent option
        fd = formObj.getFieldItem("married");

        // retrieve the dictionaries for "yes" radio button
        // note: CF8 iText version does not have the getWidgetRef(index) method or getDirectObject()
         dictYes = reader.getPdfObject( fd.widget_refs.get(0) );
        yesAction = reader.getPdfObject(dictYes.get(PdfName.AA));
        if (not IsDefined("yesAction") or not yesAction.isDictionary()) {
            yesAction = PdfDictionary.init();
        }
        // add an onFocus event to this field
        yesAction.put( PdfName.init("Fo"), PdfAction.javaScript("setReadOnly(false);", stamper.getWriter()));
        dictYes.put( PdfName.AA, yesAction );

        // retrieve the dictionaries for "no" radio button
        dictNo = reader.getPdfObject( fd.widget_refs.get(1));
        noAction = reader.getPdfObject( dictNo.get(PdfName.AA) );
        if (not IsDefined("noAction") or not noAction.isDictionary()) {
            noAction = PdfDictionary.init();
        }    
        // add an onFocus event to this field
        noAction.put( PdfName.init("Fo"), PdfAction.javaScript("setReadOnly(true);", stamper.getWriter()));
        dictNo.put(PdfName.AA, noAction);
 
         // create a submit button
        writer = stamper.getWriter();
        button = PushbuttonField.init(    writer,
                                        Rectangle.init(40, 690, 200, 710), 
                                        "submit"
                                    );
        button.setText( "validate and submit" );
        button.setOptions( PushbuttonField.VISIBLE_BUT_DOES_NOT_PRINT );
        validateAndSubmit = button.getField();
        // this will cal the validate function when the button is clicked
        validateAndSubmit.setAction( PdfAction.javaScript("validate();", stamper.getWriter()) );
        // add the button to page 1
        stamper.addAnnotation(validateAndSubmit, 1);

        WriteOutput("File created! "& outputPath &"<hr>");
    }
    catch(Any e) {
        WriteOutput("ERROR: "& e.message);
    }
    if (isDefined("stamper")) {
        stamper.close();
    }        
    if (isDefined("stream")) {
        stream.close();
    }        
</cfscript>

4 comments:

Anonymous,  April 20, 2010 at 3:06 AM  

Hi,

Using this approach - would it be possible to add javascript to an existing PDF that allowed two buttons e.g.

1) Submitted the form to server without calling the validation on required fields.

2) Validated the form only according to required fields.

Ideally, I would like to have a PDF form in the browser which can be saved at any point to allow the user to work on it until they are ready to submit. On final submission it would validate (using the validation added to the form in pdf form designer).

Thanks

cfSearching April 20, 2010 at 11:20 AM  

Yes, items #1 and #2 are possible. That is similar to what the example above does. ie Check condition X and do Y if some condition, otherwise do something else.

However, saving the form (with data) requires that extended reader rights be enabled for the form. That can only be done with Adobe software.

-Leigh

Anonymous,  April 20, 2010 at 12:27 PM  

Hi

I would be displaying the form in the browser using CF. I would want to allow submission to the server without applying the validation in the form (required fields created in livecycle designer).

This would allow the user to save a partial form.

Is this possible?

Thanks

cfSearching April 20, 2010 at 1:35 PM  

If you are working with LC (LiveCycle) forms, that is whole 'nother kettle fish. LC forms have a totally different structure than AcroForms. I do not use LC, but I believe it also has its own javascript API. So you will have to check your documentation on that one.

-Leigh

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep