Search This Blog

Wednesday, February 29, 2012

PDF Form Generation using iTextSharp


Forms are one of the very common requirement, especially if you are dealing with any government body. With aspiration towards automation, everybody seems to be moving towards PDF Forms which you can print but doesn't let you save soft copy of it.

Step one is, ofcourse, download iTextSharp library and take dependency on it. Next is following interface
   1:  public interface IFormBinder<TFormModel>
   2:  {
   3:          byte[] Bind(TFormModel model, string pdfFormTemplateFullPath);
   4:  }

This is very simple (generic) interface with just one method. Generic type defined here is Model for any given PDF form [Yes, like all our view page, here to we opted to go with one Model per Form convention.. very soon, we will understand, why]. Next we have abstract implementation for this interface, which will lift most of the burden of filling values to given PDF form. Here is the code for that class:
   1: public abstract class BaseFormBinder<TReportModel> : IFormBinder<TReportModel>
   2:     {
   3:         public virtual void BindFormFields(TReportModel reportModel, AcroFields acroFields) { }
   4:  
   5:         public byte[] Bind(TReportModel reportModel, string pdfTemplateFullPath)
   6:         {
   7:             var memoryStream = new MemoryStream();
   8:  
   9:             PdfStamper pdfStamper = null;
  10:             try
  11:             {
  12:                 var pdfReader = new PdfReader(new RandomAccessFileOrArray(pdfTemplateFullPath), null);
  13:  
  14:                 pdfStamper = new PdfStamper(pdfReader, memoryStream);
  15:  
  16:                 var acroFields = pdfStamper.AcroFields;
  17:  
  18:                 BindFormFields(reportModel, acroFields);
  19:  
  20:                 pdfStamper.FormFlattening = true;
  21:  
  22:                 pdfStamper.Close();
  23:             }
  24:             finally
  25:             {
  26:                 if (pdfStamper != null) pdfStamper.Close();
  27:             }
  28:  
  29:             return memoryStream.ToArray();
  30:         }
  31:     }
This base class reads source PDF form given path and uses iTextSharp library to fill in data to this form and finally returns filled pdf form as byte array.
Next step is creation of custom ActionResult as follow:
   1:  public class PdfFormResult<TFormModel> : ActionResult
   2:      {
   3:          public PdfFormResult(TFormModel model, string formTemplate)
   4:          {
   5:              Model = model;
   6:              FormTemplate = formTemplate;
   7:          }
   8:   
   9:          public TFormModel Model { get; private set; }
  10:          public string FormTemplate { get; set; }
  11:   
  12:   
  13:          public override void ExecuteResult(ControllerContext context)
  14:          {
  15:              var pdfFormTemplateFullPath = HttpContext.Current.Server.MapPath("/PdfForms/" + FormTemplate);
  16:   
  17:              var formBinder = ObjectFactory.GetInstance<IFormBinder<TFormModel>>();
  18:   
  19:              var content = formBinder.Bind(Model, pdfFormTemplateFullPath);
  20:   
  21:              var result = new FileContentResult(content, "binary/octet-stream");
  22:              context.HttpContext.Response.AddHeader("content-disposition", string.Format("attachment; filename={0}", FormTemplate));
  23:   
  24:              result.ExecuteResult(context);
  25:          }
  26:      }

This is yet another generic class, which takes report model as it’s type. Main point worth highlighting here is line 17, which uses IoC container to resolve instance of IFormBinder. To me, this is classic example of leveraging IoC to it’s full capacity and simplifying your code. For my case, IoC is StructureMap and all I have to do to configure it for above need is, define following Structuremap registry: [I defer any further explanation about this to my buddy, Jimmy]
   1:  public class MyRegistry : Registry
   2:      {
   3:          public MyRegistry()
   4:          {
   5:   
   6:              Scan(cfg =>
   7:                       {
   8:                           cfg.Assembly("PdfFormGenerator");
   9:                           cfg.With<DefaultConventionScanner>();
  10:                           cfg.ConnectImplementationsToTypesClosing(typeof(IFormBinder<>));
  11:                       });
  12:          }
  13:      }

Once all these in place, for any new pdf form, all I have to do now  is
1) Define Report Model
2) In my controller write action that returns PdfFormResult<ReportModel>
3) Define FormBinder that extends BaseFormBinder<GivenReportModel>  

For Completion here is code for all three steps above, for a sample 2 field pdf form
Report Model:
   1:  public class SampleFormModel
   2:      {
   3:          public string Name { get; set; }
   4:          public string Gender { get; set; }
   5:      }

Controller Action:
   1:   public PdfFormResult<SampleFormModel> PdfForm()
   2:          {
   3:              var model = UseSomeServiceToGetThisData();
   4:              return new PdfFormResult<SampleFormModel>(model, "SampleForm.pdf");
   5:          }

FormBinder:
   1:      public class SampleFormBinder : BaseFormBinder<SampleFormModel>
   2:      {
   3:          public override void BindFormFields(SampleFormModel reportModel, AcroFields acroFields)
   4:          {
   5:              acroFields.SetField("Name", reportModel.Name);
   6:              acroFields.SetField("GenderMale", reportModel.Gender);
   7:              acroFields.SetField("GenderFemale", reportModel.Gender);
   8:          }
   9:      }

That’s it. Yes..you are done. 

Tuesday, February 28, 2012

ASP.NET DataGrid Customization: Highlight and Select Row with Client-side JavaScript


With the built-in ASP.NET DataGrid control, you can design the template inside VS.NET (via the DataGrid Property Builder user interface). By default, you can use ItemStyle, AlternatingItemStyle, and SelectedItemStyle to control the DataGrid row style. You could add a Button Column to the DataGrid so the user can click on the button (Link Button or Push Button) to select a row in the DataGrid.
There are some shortcomings with the above built-in DataGrid behavior:
  1. ItemStyle is defined at design-time. Although you can change it at run-time, post-back is required to apply the changing style to the DataGrid. The DataGrid does not provide any visual feedback unless such operation triggers post-back.
  2. Using a select button is less obvious to the user. In comparison, in most DataGrids within desktop applications, the user can click anywhere in the row to select an individual row.
You can customize the DataGrid with client-side JavaScript to overcome the above shortcomings. Following are steps to do so:
  1. Setup the DataGrid template. Use the Select Button to add a column to use for selection in the DataGrid.
  2. Add the Event Handler for the DataGrid ItemCommand. Set the Select Button Column to be hidden (Visible="False"). You will hide this column, at the same time you can still trigger the Select ItemCommand via the client-side script and respond to it:

      private void grid_ItemCommand(object source,   
      System.Web.UI.WebControls.DataGridCommandEventArgs e)
      {
         if(e.CommandName == "Select")
         {
            grid.SelectedIndex = e.Item.ItemIndex;
            ...
            }
      }

     
  3. Attach the following client-side script:
      <script language="javascript">
       <!--
       function highlightRow(obj, newColor)
       {
         obj.style.cursor = "hand";
         // light-yellow, can be changed to whatever desired.
         obj.style.backgroundColor = "#ffffcc";
       }
     
       function dehighlightRow(obj, originalColor)
       {
         obj.style.backgroundColor = originalColor;
       }
       //-->
     </script>
     
  4. Add the following Event Handler to the DataGrid:

    You will change the cursor to Hand and highlight the current row when the user moves the mouse over the DataGrid row. The user can double-click anywhere in the row to select the row. (Select will trigger a post-back.)

      // Change this constant accordingly to the column index of the Select
      // ButtonColumn.

      const
     int SelectButtonColumnIndex = 0;
      

      private void grid_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
      {
        if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
        {
           if(grid.EditItemIndex < 0)
           {
              // By default, ButtonColumn is mapped to LinkButton.
              LinkButton button = (LinkButton) e.Item.Cells[SelectButtonColumnIndex].Controls[0];

              if(button != null)
              {
                 e.Item.Attributes["ondblclick"] = Page.GetPostBackClientHyperlink(button, "");
              }
           }
     
           if(e.Item.ItemType == ListItemType.Item)
           {
              e.Item.Attributes.Add("onmouseenter", "highlightRow(this, '" + ToHTMLColor(grid.SelectedItemStyle.BackColor) + "')");
              e.Item.Attributes.Add("onmouseleave", "dehighlightRow(this, '" + ToHTMLColor(grid.ItemStyle.BackColor) + "')");
           }
           else
           {
              e.Item.Attributes.Add("onmouseenter", "highlightRow(this, '" + ToHTMLColor(grid.SelectedItemStyle.BackColor) + "')");
            e.Item.Attributes.Add("onmouseleave", "dehighlightRow(this, '" + ToHTMLColor(grid.AlternatingItemStyle.BackColor) + "')");
          }
        }
      }
Furthermore, you can create your own version of the DataGrid by inheriting the built-in DataGrid. With this, you can encapsulate all code above within your own control. This way the ASP.NET page developer can use this DataGrid the same as the built-in DataGrid while having all the customization available.

Click/select Row in ASP.NET GridView or HTML Table


Make entire Table Row clickable/selectable by adding "onclick" event and formatting features via Javascript and CSS3


The suggested technique applies to ASP.NET GridView objects, and essentially to any HTML Tabletr elements. First, it makes the entire GridView row object (rendered as "tr" element) "clickable" by adding the "onclick" event (see Listing 1 coded in C#). The associated event handle procedure performs the custom formatting of the clicked/selected row to make it visually different from the others in the same table (see Listing 2 for JavaScript code snippet implementing this feature). 

Demo

The functional demo of the embedded YouTube Video Player, demonstrating the suggested technique,as the image below to open the link)

YouTube Embedded Video

 
Fig 1. Demo snapshot demonstrates the Table row corresponding to item 6 has been selected by modifying text attributes in the first cell.

Listing 1: Add onclick event (C# code behind)

    protected void GridView1_RowDataBound(Object sender, GridViewRowEventArgs e){
        if (e.Row.RowType == DataControlRowType.DataRow){
            // javascript function to call on row-click event
            e.Row.Attributes.Add("onClick", "javascript:void SelectRow(this);");
        }
    }
 

Listing 2: Add JavaScript function to the page head section

<script type="text/javascript">
         // format current row
         function SelectRow(row) {
             var _selectColor = "#303030";
             var _normalColor = "#909090";
             var _selectFontSize = "3em";
             var _normalFontSize = "2em";
             // get all data rows - siblings to current
             var _rows = row.parentNode.childNodes;
             // deselect all data rows
             try {
                 for (i = 0; i < _rows.length; i++) {
                     var _firstCell = _rows[i].getElementsByTagName("td")[0];
                     _firstCell.style.color = _normalColor;
                     _firstCell.style.fontSize = _normalFontSize;
                     _firstCell.style.fontWeight = "normal";
                 }
             }
             catch (e) { }
             // select current row (formatting applied to first cell)
             var _selectedRowFirstCell = row.getElementsByTagName("td")[0];
             _selectedRowFirstCell.style.color = _selectColor;
             _selectedRowFirstCell.style.fontSize = _selectFontSize;
             _selectedRowFirstCell.style.fontWeight = "bold";
         }
</script>

Development Notes

Note 1: Event-binding could also be done on the client side, for example, using jQuery .click()event. For mostly didactic purpose, it's implemented here using C# and DataRowBound event to be consistent with core .NET technology set.
 
Note 2: In order to select/play the first item in the grid on "page load" event, use the jQuery code snippet shown below (it inserts the small time delay):
$(document).ready(function () { 
  // start the first item after small delay 
  setTimeout('PlayRowItem()', _initDelay); 
});
 
Note 3: As it's been pointed out in comments thread, visual effects could be applied to the selected row by using a pair of addClass() and removeClass() jQuery methods. Styling the rows via CSS3 and jQuery rather than directly modifying elements' properties via jQuery as shown in Listing 2. should be considered a preferable approach for future web development, but be aware that certain deficiencies have been reported in regards to these methods prior to jQuery version 1.4.2 (starting from that version and going forward, both methods works seemingly well in all major web browsers).
 

How can I display a pointer when I point my mouse in the gridview instead of a cursor???


****************************************************************
If
e.Row.RowType = DataControlRowType.DataRow Then
e.Row.Attributes.Add(
"onmouseout", this.style.cursor="cursor"))

e.Row.Attributes.Add(
"onmouseover", this.style.cursor="hand"))


End If
****************************************************************
 effect is achieved by adding code which fires onmouseover and onmouseout as in
e.Row.Attributes.Add(
"onmouseover", this.style.cursor="hand"))

e.Row.Attributes.Add(
"onmouseout", this.style.cursor="cursor"))