Friday, February 1, 2013

Shadowbox & ASP.NET

Shadowbox makes it very easy to load full pages in a model dialogue. However things can get a little complicated if you have to pass data between pages in ASP.NET. While this posting illustrates a solution, it is probably not the most elegant. This example was done with VS 2010 and ASP.NET 4.

First thing to do is add Shadowbox to the project. You can find it on NuGet so it is very simple. Then, add the script and css references in Site.Master in the head section:

<head runat="server">
   <title></title>
   <link href="~/Styles/Site.css" rel="stylesheet" type="text/css" />
   <script src="Scripts/jquery-1.4.1.js" type="text/javascript"></script>
   <script src="Scripts/Shadowbox/js/shadowbox.js" type="text/javascript"></script>
   <link href="Scripts/ShadowBox/css/shadowbox.css" rel="stylesheet" type="text/css" />
   <asp:ContentPlaceHolder ID="HeadContent" runat="server"></asp:ContentPlaceholder>
</head>
 
Basically the scenario is, input fields are displayed on a page with a search capability. When the "Find" hyperlink is clicked, it will launch Shadowbox and display another page. We will simulate finding a contact. On the parent page, there will be some labels, text boxes, hyperlinks, and hidden fields. Remove all code from Default.aspax and drop this in:

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebForms._Default" >

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent"></asp:Content>
<asp:ContentID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">


<div id="container"
   <asp:Label runat="server" ID="lblPrimaryContact">Primary Contact</asp:Label>
   <asp:TextBox runat="server" ID="txtPrimaryContact"&gt/</asp:TextBox>
   <asp:HyperLink runat="server" ID="hlnkPrimaryFind" rel="shadowbox">Find</asp:HyperLink>
   <input type="hidden" id="primaryContact" value="" />
   <br />
   <asp:Label runat="server" ID="lblSecondaryContact">Primary Contact</asp:Label>
   <asp:TextBox runat="server" ID="txtSecondaryContact"&gt/</asp:TextBox>
   <asp:HyperLink runat="server" ID="hlnkSecondaryFind" rel="shadowbox">Find</asp:HyperLink>
   <input type="hidden" id="secondaryContact" value="" />
   <br />
   <input type="hidden" id="stateClickManager" value="" />
   <br />
   <br />
   <br />
   <br />
   <asp:Label runat="server" ID="hiddenLabel1"></asp:Label>
   <input type="hidden" id="hdnPrimary" value="" />
   <br />
   <asp:Label runat="server" ID="hiddenLabel2"></asp:Label>
   <input type="hidden" id="hdnSecondary" value="" />
</div>

</asp:Content>
 
Wiring up Shadowbox is simple. All you need to do is set the rel attribute value to shadowbox on a hyperlink and configure the links in the code behind. Notice the html hidden field with the id of stateClickManager. This hidden field will hold the value of which hyperlink was clicked so we can determine which text box in the parent should be filled from the search on the child page. Let's take care of the code behind quick, Default.aspx.cs:
public partial class _Default : System.Web.UI.Page
{
   protected void Page_Load(object sender, EventArgs e)
   {
      if (!Page.IsPostBack)
      {
         hlnkPrimaryFind.NavigateUrl = "~/FindContact.aspx";
         hlnkSecondaryFind.NavigateUrl = "~/FindContact.aspx";
      }
   }
}

To keep things simple, we'll drop JavaScript in the page right above the div id container:
<script type="text/javascript">
   function setUp() {
      // get reference to primary contact textbox
      var primaryContactTextbox = "<%=txtPrimaryContact.ClientID %>";
      // get reference to secondary contact textbox
      var secondaryContactTextbox = "<%=txtSecondaryContact.ClientID %>";
  
      // get reference to asp.net label
      var label = "<%=hiddenLabel1.ClientID %>";
      var label2 = "<%=hiddenLabel2.ClientID %>";

      // get reference to html hidden field
      var primaryHidden = document.getElementById('primaryContact');
      // assign primaryContactTextbox to primaryHidden value prop
      primaryHidden.value = primaryContactTextbox;
      // get refernce to html hidden field
      var hdnPrimary= document.getElementById('hdnPrimary');
      // hdnPrimary value prop contains asp.net label
      hdnPrimary.value = label;

      // get reference to html hidden field
      var secondaryHidden = document.getElementById('secondaryContact');
      // assign secondaryContactTextbox to secondaryHidden value prop
      secondaryHidden.value = secondaryContactTextbox;
      // get refernce to html hidden field
      var hdnSecondary = document.getElementById('hdnSecondary');
      // hdnSecondary value prop contains asp.net label
      hdnSecondaryLabel.value = label2;
   }

   $(document).ready(function () {
      $('a').click(function (event) {
         var stateClickManager = document.getElementById('stateClickManager');
         stateClickManager.value = event.target.id;
      });

      Shadowbox.init({
         onOpen: setUp
      });
   });
</script>
 
Let's drop in the child page. Add new aspx page called FindContact. This will basically act as the search form.

<%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeBehind="FindContact.aspx.cs" Inherits="WebForms.FindContact" >

<asp:Content runat="server" ID="BodyContent" ContentPlaceHolderID="MainContent">
   <div id="container">
      <asp:Label runat="server" ID="lblFirstName">First Name</asp:Label>
      <asp:TextBox runat="server" ID="txtFirstName"></asp:TextBox>
      <br /><br />
      <asp:Label runat="server" ID="lblLastName">Last Name</asp:Label>
      <asp:TextBox runat="server" ID="txtLastName"></asp:TextBox>
      <asp:Button runat="server" ID="btnFind" Text="Find" OnClick="SearchForContact" ClientIDMode="Static" />
   </div>
   <br /><br />
   <div>
      <asp:ListBox runat="server" ID="searchResults" Width="400px" />
      <br /><br />
      <input type="button" id="btnClose" value="Select Contact" onclick="ClosePage()" />
   </div>
</asp:Content>
 
The code behind, FindContact.aspx.cs, is quite simple:

public partial class FindContact : System.Web.UI.Page
{
   protected void Page_Load(object sender, EventArgs e)
   {
   }

   protected void SearchForContact(object sender, EventArgs e)
   {
      // just hardcoding some data to use
      searchResults.Items.AddRange(new ListItem[]
      {
         new ListItem("Sheldon Cooper"),
         new ListItem("Leonard Hoffstadter")
      });
   }

   protected void btnSelectContact_Click(object sender, EventArgs e)
   {
      // simple redirect would work here
      Response.Redirect("Default.aspx?SelectedContact=" + searchResults.SelectedItem.Text);
   }
}
 
 
Again, to keep things simple, JavaScript will be in the page. The script here will be taking the selected values and sentting them to the text boxes in the parent.
 
<script type="text/javascript">
function ClosePage() {
   var shadowBox = window.parent.Shadowbox;
   var selectedItem = document.getElementById('<%=searchResults.ClientID %>').value;
   var stateManager = window.parent.document.getElementById('stateClickManager');
   var whoClicked = stateManager.value;

   var hiddenContactField;
   var textbox;
   var hiddenInput;
   var hiddenInputText;

   switch (whoClicked) {
      case "MainContent_hlnkPrimaryFind":
         // grab reference to primaryContact hidden field
         hiddenContactField = window.parent.document.getElementById('primaryContact');
         // hiddenContactField.value should be primaryContactTextBox
         textbox = window.parent.document.getElementById(hiddenContactField.value);
         // get reference to html hidden input
         hiddenInput = window.parent.document.getElementById('hdnPrimary');
         // get hiddenInput, assign value prop
         hiddenInputText = window.parent.document.getElementById(hiddenInput.value);
         break;

      case "MainContent_hlnkSecondaryFind":
         // grab reference to secondaryContact hidden field
         hiddenContactField = window.parent.document.getElementById('secondaryContact');
         // hiddenContactField.value should be secondaryContactTextBox
         textbox = window.parent.document.getElementById(hiddenContactField.value);
         // get reference to html hidden input
         hiddenInput = window.parent.document.getElementById('hdnSecondary');
         // get hiddenInput, assign value prop
         hiddenInputText = window.parent.document.getElementById(hiddenInput.value);
         break;
   }

   // will display the hidden field in parent
   $(hiddenInputText).text(selectedItem);
   // will assign selected item to textbox in parent
   textbox.value = selectedItem;
   shadowBox.close();
}
</script>
 
Things should be ready to go. Run, the screen....
Click Find next to Primary Contact, this will launch Shadowbox....
 

As a shortcut, click the Find button....

 
Select Sheldon Cooper, then click Select Contact....
 

 
Sheldon now appears in the Primary Contact textbox, also, the hidden field is displayed....
 
Select Find next to Secondary Contact, this will launch Shadowbox again....
Select Find again, select Leonard Hoffstadter, click Select Contact....
Leonard now appears in the Secondary Contact textbox, also, the hidden field is displayed....