Sunday, May 6, 2012

Targeting a select list of recipients of your notification by using jQuery Ajax and ASP.NET


Topics discussed: jQuery, jQuery Ajax, ASP.NET partial page rendering, Javascript

Scenario: This blog explains a case typical of a social web app, where the user can select a list of intended recipients of a publication (e.g.: a blog post, a text message, an email, or some other kind of notification.) This would be a case when the user does not want to share information with the whole community, but rather with only a subset of select users (e.g.: friends, colleagues, followers, group members, etc.)

The page that contains the data-entry form enables the user to select his/her target audience by making behind-the-scenes jQuery Ajax calls to the server and refreshing only a portion of the page’s UI.

Let’s examine the code in different layers of the application:

The Client Code:

It consists of jQuery Ajax calls to a server side ASP.NET code, and Javascript helper methods:
<head>
    …
   1.   $("a#hlSavedList").click(function () {
   2.       var userId = $get('<%=hfUserId.ClientID%>').value;
   3.       var sUrl = "GetRecipientsByAjax.aspx?userId=" + userId;
   4.        $.get(sUrl, function (result) {
   5.                $('#divRecipientList').html(result);
             });
        });
   …
</head>

The above jQuery code executes (line 1 above) when a hyperlink with ID= hlSavedList is clicked. The html of this hyperlink is shown below:

<a href="#links" ID="hlSavedList" name="hlSavedList">Get my list</a>

In order to retrieve the user’s list, we need to reference user’s UserId stored in a hidden field that is placed on the ASP.NET page:
<asp:HiddenField ID="hfUserId" runat="server" />

And jQuery accesses the value of this HiddenField with this line of code (line 2 above):

var userId = $get('<%=hfUserId.ClientID%>').value;

The HiddenField can be assigned the UserId value after the user logs in (in page’s server-side code), and it would look something similar to this:

MembershipUser mu = Membership.GetUser();
Guid guidUserID = new Guid(mu.ProviderUserKey.ToString());
this.hfUserId.Value = guidUserID.ToString();

Alternatively, the UserId can be read from a Session object if it has stored the value earlier:

if (Session[“UserId”] != null)
{
        this.hfUserId.Value = Session[“UserId”].ToString();
}

Next, we can call our target ASP.NET page to do the work and return the HTML markup that will populate only a portion (a <div> element) of the page (lines 3, 4, and 5):

var sUrl = "GetRecipientsByAjax.aspx?userId=" + userId;

$.get(sUrl, function (result) {
      $('#divRecipientList').html(result);
});


The Server Code:
The server code exists in the GetRecipientsByAjax.aspx page’s code-behind, and it will not execute like typical pages, rather it will return only a string representing the DataList’s HTML markup:

protected void Page_Load(object sender, EventArgs e)
{
   if (Request.QueryString["userId"] != null)
   { 
        GetAudienceByUser(Request.QueryString["userId"].ToString());
   }
   else
   {
      Response.Write("<table><tr><td>Error: No query string value in url.</td></tr></table>");
      Response.End();
   }
}

This method binds the data to the DataList control and then calls the helper method GetHtml() which retrieves the controls HTML, places it in the page’s Response and ends the processing on the server by calling Response.End();

 //*******************************************
private void GetAudienceByUser(string userId)
{
   try
   {
      //Get the data from the data source:
      this.dataListAudience.DataSource =
    DataAccessLayer.GetAudienceByUser(userId);
      this.dataListAudience.DataBind();

      if (this.dataListAudience.Items.Count > 0)
      {
          Response.Cache.SetCacheability(HttpCacheability.NoCache);
          string sRet = GetHtml(this.dataListAudience);
          Response.Write(sRet);
      }
      else
      {
          Response.Cache.SetCacheability(HttpCacheability.NoCache);
          Response.Write(@"<table width=""460""><tr><td
                    style=""text-align:center;color:red;height:30px;"">
                    No contacts in your Saved List.
                    </td></tr></table>");
      }
   }
   catch (SqlException sqlEx)
   {
      Response.Cache.SetCacheability(HttpCacheability.NoCache);
      Response.Write("Database Error: " + sqlEx.Message);
   }
   catch (Exception ex)
   {
      Response.Cache.SetCacheability(HttpCacheability.NoCache);
      Response.Write("Generic Error: " + ex.Message);           
   }
   finally
   {
      Response.End();
   }
}

 //*****************************************
public static string GetHtml(Control oCtrl)
{
   StringBuilder sbHtml = new StringBuilder();
   HtmlTextWriter oWriter = new HtmlTextWriter(new StringWriter(sbHtml));

   try
   {
      //Get the control’s HTML
      oCtrl.RenderControl(oWriter);
   }
   finally
   {
      oWriter.Close();
   }
   return sbHtml.ToString();
}

The DataList control in the .aspx page:
<asp:DataList ID="dataListAudience" runat="server"
                         RepeatColumns="2" RepeatDirection="Vertical"
                         DataKeyField="User_Id" EnableViewState="true"
                         Width="500">
    <HeaderTemplate>
        <table width="460" cellpadding="0" cellspacing="0" border="0">
           <tr><td style="height:6px;"></td></tr>
           <tr><td align="center"><strong>Audience List</strong></td></tr>
           <tr><td style="height:10px;"></td></tr>
        </table>
    </HeaderTemplate>                        
    <ItemTemplate>
        <table width="215" cellpadding="0" cellspacing="0" border="0">
           <tr>                                                  
               <td width="215" align="right" valign="bottom">
                   <%# Eval("Email") %>&nbsp;
                   <input type="checkbox" name="audience"
                                value='<%# Eval("AudienceUser_Id") %>'
                                title='<%# Eval("Email") %>' />
               </td>                           
           </tr>                                              
        </table>                            
    </ItemTemplate>
    <FooterTemplate>
        <table width="460" cellpadding="0" cellspacing="0" border="0">
             <tr style="height:20px;"><td></td></tr>
        </table>
     </FooterTemplate>         
</asp:DataList>

 The rendered HTML that is returned to the client is the following text as shown in Fiddler2:

<table id="dataListAudience" cellspacing="0" border="0"
            style="width:500px;border-collapse:collapse;">
<tr>
      <td colspan="2">
            <table width="460" cellpadding="0" cellspacing="0" border="0">
                <tr><td style="height:6px;"></td></tr>
                <tr><td align="center"><strong>Audience List</strong></td></tr>
                <tr><td style="height:10px;"></td></tr>
            </table>
      </td>
</tr>

<tr>
      <td>
          <table width="215" cellpadding="0" cellspacing="0" border="0">
              <tr>
                                                  
                   
<td width="215" align="right" valign="bottom">
                            user0@adventureworks.com&nbsp;

                            <input type="checkbox" checked="checked" name="Audience"
                                        value='44813d2c-b9ac-4a8a-aab1-4a25bba1a466'
                                        title=' user0@adventureworks.com ' />
                   </td>
                           
              
</tr>
                                              
          
</table>
                            
      
</td>

      <td>
           <table width="215" cellpadding="0" cellspacing="0" border="0">
              <tr>
                                                  
                
<td width="215" align="right" valign="bottom">
                    user1@adventureworks.com&nbsp;

                    <input type="checkbox" checked="checked" name="Audience"
                                value='f8213e5b-53d9-4102-9bfa-9363f5ec82b0'
                                title='user1@adventureworks.com' />
                 </td>
                           
             
</tr>
                                              
          
</table>
                            
        
</td>
     </tr>

     <tr>
            <td colspan="2">
                <table width="460" cellpadding="0" cellspacing="0" border="0">

                     <tr style="height:20px;"><td></td></tr>
                </table>
            </td>
     </tr>

</table>


The returned HTML shown in Fiddler2 (the source):


The design view:


The architecture:

Thursday, May 3, 2012

Viewing images with the Slimbox 2 jQuery plugin and ASP.NET

To use Slimbox 2, the .aspx page should reference the needed jQuery and the plugin files:

<script type="text/javascript" src="../Scripts/jquery-1.4.1.min.js"></script>
<script type="text/javascript" src="../Scripts/slimbox2.js"></script>
<link rel="stylesheet" href="../Styles/slimbox2.css" type="text/css" media="screen" />



It is rather straightforward to display images by using Slimbox 2 and static html. The images to be displayed take the following format:

<a href="../images/imageLarge.jpg" rel="lightbox-group" title="Photo 38">
<img src="../images/imageThumb.jpg" style="border-width:4px; border-color:#666666;" />
</a>  
However, when the list of images comes from a data source, the above html has to be generated dynamically for each image. One approach would be to place an html table in the .aspx page and place an <asp:Literal> control which will hold the <tr></tr> and <td></td> tags needed to hold the rows and cells for images:
<table border="0" cellpadding="2" cellspacing="2" style="width:auto">
      <asp:Literal ID="litThumbnails" runat="server"></asp:Literal>               
</table>       
The literal control is populated by the method in page's code-behind, which can be called in the Page_Load event handler:
protected void Page_Load(object sender, EventArgs e)
{
            if (!Page.IsPostBack)
            {
       GenerateThumbnailHtml();
             }
}

private void GenerateThumbnailHtml()
{
            StringBuilder strBld = new StringBuilder();
            //We need to generate html similar to the code below for each image:
            //<td align="left" valign="bottom">
            //    <a href="Images/38.jpg" rel="lightbox-group" title="Photo 38">
            //          <img src="Images/38_th.jpg"
            //                   style="border-width:4px; border-color:#666666;" />
            //    </a>
            //</td>

           //Get the image urls from DB:
            MyImageList imageList = DataAccessLayer. ImageDB.GetList ();

            if (imageList != null)
           {
                if (imageList.Count > 1)
                {
                    int intNumOfImages = imageList.Count;
                    const int intNumOfDesiredImagesPerRow = 8;
                    int intCountOfImagesInCurrentRow = 0;

                    strBld.Append("<tr>");
         for (int ct = 1; ct <= intNumOfImages; ct++)
                    {
                        //MyImage is a custom object with properties:
                        //mi.SmallImageFile:
                        //Thumbnail image’s relative path e.g.: ../images/myThumb.jpg
                        //mi.LargeImageFile:
                        //Large image’s relative path e.g.: ../images/myThumb.jpg
                        // mi.Description: Image’s description
                        MyImage mi = imageList[ct - 1];

                        strBld.Append(String.Format(@"<td align=""left"" valign=""bottom"">
                         <a href=""{0}"" rel=""lightbox-group"" title=""{1}"">
                         <img src=""{2}"" style=""border-width:4px; border-color:#778899;"" />
                         </a></td>",  mi.LargeImageFile, mi.Description, mi.SmallImageFile));
                       
                       intCountOfImagesInCurrentRow++;
                       
                       //8 Images per row:                       
                       if (ct < intNumOfImages)
                       {
                            if (ct % intNumOfDesiredImagesPerRow == 0)
                            {
                                strBld.Append("</tr><tr>");
                                intCountOfImagesInCurrentRow = 0;
                            }
                        }
                        else if (ct == intNumOfImages)
                        {
                            if (ct % intNumOfDesiredImagesPerRow == 0)
                            {
                                strBld.Append("</tr>");
                            }
                            else
                            {
                               strBld.Append(String.Format("<td colspan=\"{0}\">",            
                               intNumOfDesiredImagesPerRow - intCountOfImagesInCurrentRow));
                               strBld.Append("</td></tr>");
                            }
                        }
                    }                
                    if (strBld.Length == 4) // Contains only <tr> so far 
                    {
                        this.litThumbnails.Text = "";
                    }
                    else
                    {
                        this.litThumbnails.Text = strBld.ToString();
                    }
                    this.litThumbnails.Visible = true;
                }
                else
                {
                    this.litThumbnails.Visible = false;
                }
            }
            else
            {
                this.litThumbnails.Visible = false;
            }
       }

The list of images is retrieved from Db and the <tr> and <td> tags are generated dynamically for each image in the list. When the list is completed, the contents of the StringBuilder object containing the generated html is assigned to the .Text property of the Literal control.

this.litThumbnails.Text = strBld.ToString();

See the results below: